AWS SESの受信メールを暗号化してs3に保存しLambdaで読み込む
SESでメール受信を行う際にS3にデータの保存ができる。
この時「Encrypt Message」にチェックを入れると、KMSを使ってS3に保存するデータの暗号化を行うことができる。
この暗号化されたデータをLambdaで複合しようとした際にハマったので対応方法を載せておく。
ドキュメントを読んでもイマイチよく分からず、↓を参考にして複合処理を実装した。
'use strict';
const crypto = require("crypto");
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
const kms = new AWS.KMS();
const s3Bucket = process.env.S3_BUCKET;
const s3PathPrefix = process.env.S3_PATH_PREFIX;
// https://stackoverflow.com/questions/54543410/decrypt-ses-message-from-s3-with-kms-node
const decryptS3Message = async (objectData) => {
const metadata = objectData.Metadata || {};
const kmsKeyBase64 = metadata['x-amz-key-v2'];
const iv = metadata['x-amz-iv'];
const tagLen = (metadata['x-amz-tag-len'] || 0) / 8;
let algo = metadata['x-amz-cek-alg'];
const encryptionContext = JSON.parse(metadata['x-amz-matdesc']);
switch (algo) {
case 'AES/GCM/NoPadding':
algo = `aes-256-gcm`;
break;
case 'AES/CBC/PKCS5Padding':
algo = `aes-256-cbc`;
break;
default:
throw new Error('Unsupported algorithm: ' + algo);
}
if (typeof (kmsKeyBase64) === 'undefined') {
return null;
}
const kmsKeyBuffer = Buffer.from(kmsKeyBase64, 'base64');
const returnValue = await kms.decrypt({ CiphertextBlob: kmsKeyBuffer, EncryptionContext: encryptionContext }).promise()
.then((res) => {
const body = objectData.Body
const data = body.slice(0, -tagLen);
const decipher = crypto.createDecipheriv( algo, res.Plaintext, Buffer.from(iv, 'base64'));
if (tagLen !== 0) {
const tag = body.slice(-tagLen);
decipher.setAuthTag(tag);
}
let dec = decipher.update(data, 'binary', 'utf8');
dec += decipher.final('utf8');
return dec;
}).catch((err) => {
console.error(err.message);
throw new Error('Not able to decrypt message: ', err);
});
return returnValue;
};
exports.handler = async function(event, context) {
const messageId = event.Records[0]['ses']['mail']['messageId'];
const request = {
Bucket: s3Bucket,
Key: `${s3PathPrefix}${messageId}`,
};
try {
const objectData = await s3.getObject(request).promise();
const decryptedMessage = await decryptS3Message(objectData);
console.log(decryptedMessage);
return { status: 'success' };
} catch (error) {
console.error(error, error.stack);
return Error;
}
};
これで複合したメールデータをLambdaで扱えるようになった。
RubyやGoのSDKではS3::Encryption::Clientが使えるようだが、jsやpythonにはないので今回のように独自実装する必要があり大変。
js SDKでもS3::Encryption::Clientサポート来ないかなぁ。