Skip to content

Instantly share code, notes, and snippets.

@shrys
Last active June 10, 2020 13:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shrys/2d6b4c0c85095a628c261eaeeef34a8b to your computer and use it in GitHub Desktop.
Save shrys/2d6b4c0c85095a628c261eaeeef34a8b to your computer and use it in GitHub Desktop.
Nodejs implementation to obtain smtp password from signed secret key for AWS SES using node/crypto
const c = require('crypto');
const utf8 = require('utf8');
const key = 'YOUR_SECRET_KEY';
const region = 'us-east-1';
const date = '11111111';
const service = 'ses';
const terminal = 'aws4_request';
const message = 'SendRawEmail';
const versionInBytes = [0x04];
function sign(key, msg) {
return c.createHmac('sha256', Buffer.from(key.map(a => a.charCodeAt(0))))
.update(utf8.encode(msg))
.digest('latin1')
.split('');
}
let signature = sign((utf8.encode('AWS4' + key)).split(''), date);
signature = sign(signature, region);
signature = sign(signature, service);
signature = sign(signature, terminal);
signature = sign(signature, message);
const signatureAndVersion = versionInBytes.slice(); //copy of array
signature.forEach(a => signatureAndVersion.push(a.charCodeAt(0)));
const smtpPassword = Buffer.from(signatureAndVersion).toString('base64');
console.log(smtpPassword);
{
"name": "sample",
"version": "1.0.0",
"description": "",
"main": "indext.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@types/node": "^14.0.12",
"@types/utf8": "^2.1.6",
"express": "^4.17.1",
"express.js": "^1.0.0",
"utf8": "^3.0.0"
}
}
{
"compilerOptions": {
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}
@Cloudrage
Copy link

Hi @shrys,

On my side, I've some problems with that code :

  • Parameter 'key' implicitly has an 'any' type.
  • Parameter 'msg' implicitly has an 'any' type.
  • Parameter 'a' implicitly has an 'any' type.
  • Argument of type '"binary"' is not assignable to parameter of type 'HexBase64Latin1Encoding'.

@shrys
Copy link
Author

shrys commented Jun 9, 2020

Hi @Cloudrage, thank you for pointing it out. I have updated the gist with working code and the configurations I have been using.
The problems I think are caused by typescript language in vscode, I've used the "noImplicitAny": false, option to suppress them. For argument type, I have corrected it to latin1 as you rightly pointed out. Docs mention it as type HexBase64Latin1Encoding = "latin1" | "hex" | "base64";

@Cloudrage
Copy link

Thanks, better with latin1 indeed.
For the others, noImplicitAny can be set to true if desired with that code :

// Convert to SMTP base64 password
    const crypto = require('crypto');
    const utf8 = require('utf8');
    const key = MyKey
    const region = MyRegion;

    const date = '11111111';
    const service = 'ses';
    const terminal = 'aws4_request';
    const message = 'SendRawEmail';
    const versionInBytes = [0x04];
    
    function sign(key: { map: (arg0: (a: any) => any) => ArrayBuffer | SharedArrayBuffer; }, msg: string) {
      return crypto.createHmac('sha256', Buffer.from(key.map((a: string) => a.charCodeAt(0))))
        .update(utf8.encode(msg))
        .digest('latin1')
        .split('');
    }
    
    let signature = sign((utf8.encode('AWS4' + key)).split(''), date);
    signature = sign(signature, region);
    signature = sign(signature, service);
    signature = sign(signature, terminal);
    signature = sign(signature, message);
    
    const signatureAndVersion = versionInBytes.slice(); //copy of array
    
    signature.forEach((a: string) => signatureAndVersion.push(a.charCodeAt(0)));
    
    const smtpPassword = Buffer.from(signatureAndVersion).toString('base64');

But a SMTP connection test don't seems to work, strange. I follow that proc :
https://docs.aws.amazon.com/fr_fr/ses/latest/DeveloperGuide/send-email-smtp-client-command-line.html

I've a :

535 Authentication Credentials Invalid
530 Authentication required

I'm also testing with working credentials from an application using SES; so maybe I doing something wrong with the AWS testing proc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment