1package mox
2
3import (
4 "bytes"
5 "crypto/aes"
6 "crypto/cipher"
7 "encoding/base64"
8 "encoding/binary"
9 "fmt"
10)
11
12var idCipher cipher.Block
13var idRand []byte
14
15func init() {
16 // Init for tests. Overwritten in ../serve.go.
17 err := ReceivedIDInit([]byte("0123456701234567"), []byte("01234567"))
18 if err != nil {
19 panic(err)
20 }
21}
22
23// ReceivedIDInit sets an AES key (must be 16 bytes) and random buffer (must be
24// 8 bytes) for use by ReceivedID.
25func ReceivedIDInit(key, rand []byte) error {
26 var err error
27 idCipher, err = aes.NewCipher(key)
28 idRand = rand
29 return err
30}
31
32// ReceivedID returns an ID for use in a message Received header.
33//
34// The ID is based on the cid. The cid itself is a counter and would leak the
35// number of connections in received headers. Instead they are obfuscated by
36// encrypting them with AES with a per-install key and random buffer. This allows
37// recovery of the cid based on the id. See subcommand cid.
38func ReceivedID(cid int64) string {
39 buf := make([]byte, 16)
40 copy(buf, idRand)
41 binary.BigEndian.PutUint64(buf[8:], uint64(cid))
42 idCipher.Encrypt(buf, buf)
43 return base64.RawURLEncoding.EncodeToString(buf)
44}
45
46// ReceivedToCid returns the cid given a ReceivedID.
47func ReceivedToCid(s string) (cid int64, err error) {
48 buf, err := base64.RawURLEncoding.DecodeString(s)
49 if err != nil {
50 return 0, fmt.Errorf("decode base64: %v", err)
51 }
52 if len(buf) != 16 {
53 return 0, fmt.Errorf("bad length, got %d, expect 16", len(buf))
54 }
55 idCipher.Decrypt(buf, buf)
56 if !bytes.Equal(buf[:8], idRand) {
57 return 0, fmt.Errorf("rand mismatch")
58 }
59 cid = int64(binary.BigEndian.Uint64(buf[8:]))
60 return cid, nil
61}
62