1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package fxa
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/url"
"strings"
"time"
"golang.org/x/crypto/hkdf"
"golang.org/x/crypto/pbkdf2"
)
const (
protocolVersion = "identity.mozilla.com/picl/v1/"
pbkdf2Rounds = 1000
stretchedPWLen = 32
hkdfLen = 32
)
func deriveAuthPW(email, password string) ([]byte, error) {
salt := []byte(protocolVersion + "quickStretch:" + email)
quickStretchedPW := pbkdf2.Key([]byte(password), salt, pbkdf2Rounds, stretchedPWLen, sha256.New)
hkdfSalt := []byte{0x00}
info := []byte(protocolVersion + "authPW")
hkdfReader := hkdf.New(sha256.New, quickStretchedPW, hkdfSalt, info)
authPW := make([]byte, hkdfLen)
if _, err := io.ReadFull(hkdfReader, authPW); err != nil {
return nil, err
}
return authPW, nil
}
func deriveHawkCredentials(tokenHex, context string) (id string, key []byte, err error) {
tokenBytes, err := hex.DecodeString(tokenHex)
if err != nil {
return "", nil, fmt.Errorf("invalid token hex: %w", err)
}
info := []byte(protocolVersion + context)
hkdfReader := hkdf.New(sha256.New, tokenBytes, nil, info)
out := make([]byte, 3*32)
if _, err := io.ReadFull(hkdfReader, out); err != nil {
return "", nil, err
}
return hex.EncodeToString(out[:32]), out[32:64], nil
}
func hawkHeader(method, rawURL, hawkID string, hawkKey []byte, payload string) (string, error) {
u, err := url.Parse(rawURL)
if err != nil {
return "", err
}
nonce := make([]byte, 6)
if _, err := rand.Read(nonce); err != nil {
return "", fmt.Errorf("generating nonce: %w", err)
}
nonceStr := hex.EncodeToString(nonce)
ts := fmt.Sprintf("%d", time.Now().Unix())
port := u.Port()
if port == "" {
if u.Scheme == "https" {
port = "443"
} else {
port = "80"
}
}
var payloadHash string
if payload != "" {
h := sha256.New()
h.Write([]byte("hawk.1.payload\napplication/json\n"))
h.Write([]byte(payload))
h.Write([]byte("\n"))
payloadHash = hex.EncodeToString(h.Sum(nil))
}
normalized := strings.Join([]string{
"hawk.1.header",
ts,
nonceStr,
strings.ToUpper(method),
u.RequestURI(),
u.Hostname(),
port,
payloadHash,
"",
"",
}, "\n")
mac := hmac.New(sha256.New, hawkKey)
mac.Write([]byte(normalized))
macStr := hex.EncodeToString(mac.Sum(nil))
header := fmt.Sprintf(`Hawk id="%s", ts="%s", nonce="%s", mac="%s"`, hawkID, ts, nonceStr, macStr)
if payloadHash != "" {
header += fmt.Sprintf(`, hash="%s"`, payloadHash)
}
return header, nil
}