Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SNOW-1524258: Implement GCM encryption #966

Merged
merged 11 commits into from
Nov 21, 2024
39 changes: 24 additions & 15 deletions lib/file_transfer_agent/encrypt_util.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@

const QUERY_STAGE_MASTER_KEY = 'queryStageMasterKey';
const BASE64 = 'base64';
const DEFAULT_DATA_AAD = Buffer.from('default data aad');
const DEFAULT_KEY_AAD = Buffer.from('default key aad');
const AUTH_TAG_LENGTH = 16;
const DEFAULT_AAD = Buffer.from('');
const AUTH_TAG_LENGTH_IN_BYTES = 16;

// Material Descriptor
function MaterialDescriptor(smkId, queryId, keySize) {
Expand Down Expand Up @@ -132,6 +131,12 @@
keySize * 8
);

if (keyIv && dataAad && keyAad) {
sfc-gh-dheyman marked this conversation as resolved.
Show resolved Hide resolved
keyIv = keyIv.toString(BASE64);
sfc-gh-dprzybysz marked this conversation as resolved.
Show resolved Hide resolved
dataAad = dataAad.toString(BASE64);
keyAad = keyAad.toString(BASE64);
}

return new EncryptionMetadata(
encryptedKey.toString(BASE64),
dataIv.toString(BASE64),
Expand Down Expand Up @@ -170,24 +175,24 @@

//TODO: Add proper usage when feature is ready (SNOW-940981)
sfc-gh-dprzybysz marked this conversation as resolved.
Show resolved Hide resolved
this.encryptDataGCM = function (encryptionMaterial, data) {
const decodedKek = Buffer.from(encryptionMaterial[QUERY_STAGE_MASTER_KEY], BASE64);
const keySize = decodedKek.length;

Check warning on line 179 in lib/file_transfer_agent/encrypt_util.js

View check run for this annotation

Codecov / codecov/patch

lib/file_transfer_agent/encrypt_util.js#L178-L179

Added lines #L178 - L179 were not covered by tests

const dataIv = getSecureRandom(AES_GCM.ivSize);
const fileKey = getSecureRandom(keySize);

Check warning on line 182 in lib/file_transfer_agent/encrypt_util.js

View check run for this annotation

Codecov / codecov/patch

lib/file_transfer_agent/encrypt_util.js#L181-L182

Added lines #L181 - L182 were not covered by tests

const encryptedData = this.encryptGCM(data, fileKey, dataIv, DEFAULT_DATA_AAD);
const encryptedData = this.encryptGCM(data, fileKey, dataIv, DEFAULT_AAD);

Check warning on line 184 in lib/file_transfer_agent/encrypt_util.js

View check run for this annotation

Codecov / codecov/patch

lib/file_transfer_agent/encrypt_util.js#L184

Added line #L184 was not covered by tests

const keyIv = getSecureRandom(AES_GCM.ivSize);
const encryptedKey = this.encryptGCM(fileKey, decodedKek, keyIv, DEFAULT_KEY_AAD);
const encryptedKey = this.encryptGCM(fileKey, decodedKek, keyIv, DEFAULT_AAD);

Check warning on line 187 in lib/file_transfer_agent/encrypt_util.js

View check run for this annotation

Codecov / codecov/patch

lib/file_transfer_agent/encrypt_util.js#L186-L187

Added lines #L186 - L187 were not covered by tests
return {
encryptionMetadata: createEncryptionMetadata(encryptionMaterial, keySize, encryptedKey, dataIv, keyIv, DEFAULT_DATA_AAD, DEFAULT_KEY_AAD),
encryptionMetadata: createEncryptionMetadata(encryptionMaterial, keySize, encryptedKey, dataIv, keyIv, DEFAULT_AAD, DEFAULT_AAD),
dataStream: encryptedData
};
};

this.encryptGCM = function (data, key, iv, aad) {
const cipher = crypto.createCipheriv(AES_GCM.cipherName(key.length), key, iv, { authTagLength: AUTH_TAG_LENGTH });
const cipher = crypto.createCipheriv(AES_GCM.cipherName(key.length), key, iv, { authTagLength: AUTH_TAG_LENGTH_IN_BYTES });
if (aad) {
cipher.setAAD(aad);
}
Expand All @@ -196,13 +201,13 @@
};

this.decryptGCM = function (data, key, iv, aad) {
const decipher = crypto.createDecipheriv(AES_GCM.cipherName(key.length), key, iv, { authTagLength: AUTH_TAG_LENGTH });
const decipher = crypto.createDecipheriv(AES_GCM.cipherName(key.length), key, iv, { authTagLength: AUTH_TAG_LENGTH_IN_BYTES });
if (aad) {
decipher.setAAD(aad);
}
// last 16 bytes of data is the authentication tag
const authTag = data.slice(data.length - AUTH_TAG_LENGTH, data.length);
const cipherText = data.slice(0, data.length - AUTH_TAG_LENGTH);
const authTag = data.slice(data.length - AUTH_TAG_LENGTH_IN_BYTES, data.length);
const cipherText = data.slice(0, data.length - AUTH_TAG_LENGTH_IN_BYTES);
decipher.setAuthTag(authTag);
return performCrypto(decipher, cipherText);
};
Expand Down Expand Up @@ -244,20 +249,20 @@
const fileContent = await new Promise((resolve, reject) => {
fs.readFile(inputFilePath, (err, data) => {
if (err) {
reject(err);

Check warning on line 252 in lib/file_transfer_agent/encrypt_util.js

View check run for this annotation

Codecov / codecov/patch

lib/file_transfer_agent/encrypt_util.js#L252

Added line #L252 was not covered by tests
}
resolve(data);
});
});

const encryptedData = this.encryptGCM(fileContent, fileKey, dataIv, DEFAULT_DATA_AAD);
const encryptedData = this.encryptGCM(fileContent, fileKey, dataIv, DEFAULT_AAD);
const encryptedFilePath = await writeContentToFile(tmpDir, path.basename(inputFilePath) + '#', encryptedData);

const keyIv = getSecureRandom(AES_GCM.ivSize);
const encryptedKey = this.encryptGCM(fileKey, decodedKek, keyIv, DEFAULT_KEY_AAD);
const encryptedKey = this.encryptGCM(fileKey, decodedKek, keyIv, DEFAULT_AAD);

return {
encryptionMetadata: createEncryptionMetadata(encryptionMaterial, fileKey.length, encryptedKey, dataIv, keyIv, DEFAULT_DATA_AAD, DEFAULT_KEY_AAD),
encryptionMetadata: createEncryptionMetadata(encryptionMaterial, fileKey.length, encryptedKey, dataIv, keyIv, DEFAULT_AAD, DEFAULT_AAD),
dataFile: encryptedFilePath
};
};
Expand Down Expand Up @@ -291,13 +296,17 @@
const keyBase64 = metadata.key;
const keyIvBase64 = metadata.keyIv;
const dataIvBase64 = metadata.iv;
const dataAadBase64 = metadata.dataAad;
const keyAadBase64 = metadata.keyAad;

const decodedKek = Buffer.from(encryptionMaterial[QUERY_STAGE_MASTER_KEY], BASE64);
const keyBytes = new Buffer.from(keyBase64, BASE64);
const keyIvBytes = new Buffer.from(keyIvBase64, BASE64);
const dataIvBytes = new Buffer.from(dataIvBase64, BASE64);
const dataAadBytes = new Buffer.from(dataAadBase64, BASE64);
const keyAadBytes = new Buffer.from(keyAadBase64, BASE64);

const fileKey = this.decryptGCM(keyBytes, decodedKek, keyIvBytes, DEFAULT_KEY_AAD);
const fileKey = this.decryptGCM(keyBytes, decodedKek, keyIvBytes, keyAadBytes);

const fileContent = await new Promise((resolve, reject) => {
fs.readFile(inputFilePath, (err, data) => {
sfc-gh-dprzybysz marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -308,7 +317,7 @@
});
});

const decryptedData = this.decryptGCM(fileContent, fileKey, dataIvBytes, DEFAULT_DATA_AAD);
const decryptedData = this.decryptGCM(fileContent, fileKey, dataIvBytes, dataAadBytes);
return await writeContentToFile(tmpDir, path.basename(inputFilePath) + '#', decryptedData);
};

Expand All @@ -324,7 +333,7 @@
await new Promise((resolve, reject) => {
tmp.file({ dir: tmpDir, prefix: path.basename(inputFilePath) + '#' }, (err, path, fd) => {
if (err) {
reject(err);

Check warning on line 336 in lib/file_transfer_agent/encrypt_util.js

View check run for this annotation

Codecov / codecov/patch

lib/file_transfer_agent/encrypt_util.js#L336

Added line #L336 was not covered by tests
sfc-gh-dprzybysz marked this conversation as resolved.
Show resolved Hide resolved
}
outputFilePath = path;
outputFileFd = fd;
Expand All @@ -348,7 +357,7 @@
await new Promise((resolve, reject) => {
fs.close(outputFileFd, (err) => {
if (err) {
reject(err);

Check warning on line 360 in lib/file_transfer_agent/encrypt_util.js

View check run for this annotation

Codecov / codecov/patch

lib/file_transfer_agent/encrypt_util.js#L360

Added line #L360 was not covered by tests
}
resolve();
});
Expand All @@ -362,7 +371,7 @@
await new Promise((resolve, reject) => {
sfc-gh-dprzybysz marked this conversation as resolved.
Show resolved Hide resolved
tmp.file({ dir: tmpDir, prefix: prefix }, (err, path, fd) => {
if (err) {
reject(err);

Check warning on line 374 in lib/file_transfer_agent/encrypt_util.js

View check run for this annotation

Codecov / codecov/patch

lib/file_transfer_agent/encrypt_util.js#L374

Added line #L374 was not covered by tests
}
outputFilePath = path;
outputFileFd = fd;
Expand All @@ -372,7 +381,7 @@
await new Promise((resolve, reject) => {
fs.writeFile(outputFilePath, content, err => {
sfc-gh-dprzybysz marked this conversation as resolved.
Show resolved Hide resolved
if (err) {
reject(err);

Check warning on line 384 in lib/file_transfer_agent/encrypt_util.js

View check run for this annotation

Codecov / codecov/patch

lib/file_transfer_agent/encrypt_util.js#L384

Added line #L384 was not covered by tests
}
resolve();
});
Expand All @@ -380,7 +389,7 @@
await new Promise((resolve, reject) => {
fs.close(outputFileFd, (err) => {
if (err) {
reject(err);

Check warning on line 392 in lib/file_transfer_agent/encrypt_util.js

View check run for this annotation

Codecov / codecov/patch

lib/file_transfer_agent/encrypt_util.js#L392

Added line #L392 was not covered by tests
}
resolve();
});
Expand Down
11 changes: 4 additions & 7 deletions test/unit/file_transfer_agent/encrypt_util_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,13 @@ describe('Encryption util', function () {
}
return new createWriteStream;
},
closeSync: function () {
return;
close: function (fd, callback) {
callback(null);
}
});
mock('temp', {
fileSync: function () {
return {
name: mockTmpName,
fd: 0
};
file: function (object, callback) {
callback(null, mockTmpName, 0);
},
openSync: function () {
return;
Expand Down
Loading