UNPKG

8.39 kBJavaScriptView Raw
1/**
2 * Copyright 2017-2018 F5 Networks, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17'use strict';
18
19const assert = require('assert');
20const childProcess = require('child_process');
21const q = require('q');
22const util = require('./util');
23const cryptoUtil = require('./cryptoUtil');
24const localKeyUtil = require('./localKeyUtil');
25const Logger = require('./logger');
26
27let logger = Logger.getLogger({
28 logLevel: 'none',
29 module
30});
31
32const KEYS = require('./sharedConstants').KEYS;
33
34/**
35 * This routines are utilities for decrypting data from files on disk
36 *
37 * These routines are meant to be used locally on a BIG-IP and operate via tmsh
38 * rather than iControl REST. This is so that we do not need to take in
39 * unencrypted passwords as parameters either on the command line or via
40 * the filesystem.
41 *
42 * Notes:
43 * + Only runs locally on a BIG-IP. Cannot run on a remote BIG-IP.
44 * + Uses tmsh rather than iControl REST so that we do not need to take in a password
45 *
46 * @module
47 */
48module.exports = {
49
50 /**
51 * Decrypts data
52 *
53 * @param {String} data - Data to decrypt
54 * @param {String} privateKeyFolder - BIG-IP folder in which private key is installed
55 * @param {String} privateKeyName - Name of private key installed on BIG-IP
56 * @param {Object} [options] - Optional parameters
57 * @param {String} [options.encryptedKey] - The encrypted symmetric key. Required if symmetric encryption
58 * was used.
59 * @param {String | Buffer} [options.iv] - The initialization vector that was used for
60 * encryption. Required if symmetric encryption was used.
61 *
62 * @returns {Promise} A promise which is resolved with the decrypted data or
63 * rejected if an error occurs.
64 */
65 decryptData(data, privateKeyFolder, privateKeyName, options) {
66 assert.ok(privateKeyFolder, 'privateKeyFolder is required');
67 assert.ok(privateKeyName, 'privateKeyName is required');
68 assert.ok(data, 'data is required');
69
70 let privateKeyFile;
71 let existingPrivateKeyName;
72
73 return localKeyUtil.getExistingPrivateKeyName(privateKeyFolder, privateKeyName)
74 .then((response) => {
75 existingPrivateKeyName = response;
76 return localKeyUtil.getPrivateKeyFilePath(privateKeyFolder, existingPrivateKeyName);
77 })
78 .then((privateKeyFilePath) => {
79 if (!privateKeyFilePath) {
80 return q.reject(new Error('No private key found'));
81 }
82
83 privateKeyFile = privateKeyFilePath;
84 return localKeyUtil.getPrivateKeyMetadata(privateKeyFolder, existingPrivateKeyName);
85 })
86 .then((metadata) => {
87 if (!metadata) {
88 return q.reject(new Error('No private key metadata'));
89 }
90
91 const decryptOptions = {
92 passphrase: metadata.passphrase,
93 passphraseEncrypted: !!metadata.passphrase
94 };
95
96 if (options && options.encryptedKey && options.iv) {
97 return cryptoUtil.symmetricDecrypt(
98 privateKeyFile,
99 options.encryptedKey,
100 options.iv,
101 data,
102 decryptOptions
103 );
104 }
105 return cryptoUtil.decrypt(privateKeyFile, data, decryptOptions);
106 })
107 .then((decryptedData) => {
108 return decryptedData;
109 })
110 .catch((err) => {
111 logger.info('Error decrypting data', err && err.message ? err.message : err);
112 return q.reject(err);
113 });
114 },
115
116 /**
117 * Decrypts a secret, typically a password that was encrypted with our
118 * local private keys.
119 *
120 * This is just a shortcut for {@link decryptData}
121 *
122 * @static
123 *
124 * @param {String} password - secret to decrypt
125 *
126 * @returns {Promise} A promise which is resolved with the decrypted
127 * secret or rejected if an error occurs
128 */
129 decryptPassword(password) {
130 assert.ok(password, 'password is required');
131
132 return this.decryptData(password, KEYS.LOCAL_PRIVATE_KEY_FOLDER, KEYS.LOCAL_PRIVATE_KEY);
133 },
134
135 /**
136 * Decrypts a secret that was encrypted symmetrically, typically a large password
137 * or object (JSON) with multiple values that was encrypted with our local private keys.
138 *
139 * This is just a shortcut for {@link decryptData} with symmetric options
140 *
141 * @static
142 *
143 * @param {Object} data - Object (JSON) created by symmetric encrypt operation
144 *
145 * @returns {Promise} A promise which is resolved with the decrypted
146 * secret or rejected if an error occurs
147 */
148 symmetricDecryptPassword(data) {
149 assert.ok(data, 'data is required');
150
151 let parsedData;
152 try {
153 parsedData = typeof data === 'object' ? data : JSON.parse(data);
154 } catch (err) {
155 return q.reject(err && err.message ? err.message : err);
156 }
157
158 return this.decryptData(
159 parsedData.encryptedData,
160 parsedData.privateKey.folder,
161 parsedData.privateKey.name,
162 {
163 encryptedKey: parsedData.encryptedKey,
164 iv: parsedData.iv
165 }
166 );
167 },
168
169 /**
170 * Decrypts data from a file on disk
171 *
172 * @param {String} dataFile - File to decrypt
173 * @param {Object} [options] - Optional parameters
174 * @param {Boolean} [options.symmetric] - Data was symmetrically encrypted
175 *
176 * @returns {Promise} A promise which is resolved with the decrypted data or
177 * rejected if an error occurs.
178 */
179 decryptDataFromFile(dataFile, options) {
180 let dataToDecrypt;
181
182 assert.ok(dataFile, 'dataFile is required');
183
184 return util.readDataFromFile(dataFile)
185 .then((data) => {
186 dataToDecrypt = data.toString();
187 if (options && options.symmetric) {
188 return this.symmetricDecryptPassword(dataToDecrypt);
189 }
190 return this.decryptPassword(dataToDecrypt);
191 })
192 .catch((err) => {
193 logger.info('Error decrypting data from file', err && err.message ? err.message : err);
194 return q.reject(err);
195 });
196 },
197
198 /**
199 * Decrypts a BIG-IP configuration value.
200 *
201 * Must be run on a BIG-IP.
202 *
203 * @param {String} value - The configuragtion value to decrypt
204 *
205 * @returns {Promse} A promise which is resolved with the decrypted configuration
206 * value or rejected if an error occurs.
207 */
208 decryptConfValue(value) {
209 const deferred = q.defer();
210
211 childProcess.execFile(
212 `${__dirname}/../scripts/decryptConfValue`,
213 [value],
214 (error, stdout, stderr) => {
215 if (error) {
216 logger.info(
217 'Error decrypting conf value',
218 error && error.message ? error.message : error
219 );
220 deferred.reject(new Error(stderr));
221 } else {
222 deferred.resolve(stdout);
223 }
224 }
225 );
226
227 return deferred.promise;
228 },
229
230 setLoggerOptions(loggerOptions) {
231 const loggerOpts = Object.assign({}, loggerOptions);
232 loggerOpts.module = module;
233 logger = Logger.getLogger(loggerOpts);
234 },
235};