| 1 | // SMTP credential derivation. SES's SMTP password is not issued by an API — |
| 2 | // it is a deterministic function of the IAM secret access key and the |
| 3 | // region, which is why this package can hand an app working SMTP |
| 4 | // credentials without a second AWS call, and why the secret itself never |
| 5 | // needs to be stored anywhere (spec §3, §4). |
| 6 | package mail |
| 7 | |
| 8 | import ( |
| 9 | "crypto/hmac" |
| 10 | "crypto/sha256" |
| 11 | "encoding/base64" |
| 12 | ) |
| 13 | |
| 14 | // The four constants of AWS's published derivation. smtpDate is a literal |
| 15 | // "11111111" and NOT today's date — SES fixed it so the password does not |
| 16 | // expire; using a real date silently produces a credential SES rejects. |
| 17 | const ( |
| 18 | smtpDate = "11111111" |
| 19 | smtpService = "ses" |
| 20 | smtpTerminal = "aws4_request" |
| 21 | smtpMessage = "SendRawEmail" |
| 22 | smtpVersion = 0x04 |
| 23 | ) |
| 24 | |
| 25 | func hmac256(key []byte, data string) []byte { |
| 26 | h := hmac.New(sha256.New, key) |
| 27 | h.Write([]byte(data)) |
| 28 | return h.Sum(nil) |
| 29 | } |
| 30 | |
| 31 | // SMTPPassword derives the SES SMTP password for an IAM secret access key |
| 32 | // in region. The matching SMTP username is the access key ID, unmodified. |
| 33 | // |
| 34 | // The signing chain is date → region → service → terminal → message, seeded |
| 35 | // with "AWS4"+secret, and the result is the version byte followed by the |
| 36 | // HMAC. Every step is load-bearing and the order is not negotiable. |
| 37 | func SMTPPassword(secretKey, region string) string { |
| 38 | sig := []byte("AWS4" + secretKey) |
| 39 | for _, step := range []string{smtpDate, region, smtpService, smtpTerminal, smtpMessage} { |
| 40 | sig = hmac256(sig, step) |
| 41 | } |
| 42 | return base64.StdEncoding.EncodeToString(append([]byte{smtpVersion}, sig...)) |
| 43 | } |
| 44 | |