-
Notifications
You must be signed in to change notification settings - Fork 15
/
api_key.go
117 lines (98 loc) · 3.76 KB
/
api_key.go
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
107
108
109
110
111
112
113
114
115
116
117
package middleware
import (
"crypto/hmac"
"encoding/base64"
"errors"
"fmt"
"io"
"net/http"
"strings"
"time"
"github.com/ashirt-ops/ashirt-server/backend"
"github.com/ashirt-ops/ashirt-server/backend/database"
"github.com/ashirt-ops/ashirt-server/backend/logging"
"github.com/ashirt-ops/ashirt-server/backend/models"
"github.com/ashirt-ops/ashirt-server/signer"
sq "github.com/Masterminds/squirrel"
)
// Max allowed difference between the current time and passed Date header
const maxDateDelta = time.Hour
type UserData struct {
ID int64
Headless bool
}
func authenticateAPI(db *database.Connection, r *http.Request, requestBody io.Reader) (UserData, error) {
emptyUserData := UserData{ID: -1, Headless: false}
if err := checkDateHeader(r.Header.Get("Date")); err != nil {
return emptyUserData, backend.WrapError("Unable to parse date header (for api auth)", err)
}
// Check HMAC
accessKey, headerHMAC, err := parseAuthorizationHeader(r.Header.Get("Authorization"))
if err != nil {
return emptyUserData, backend.WrapError("Unable to parse (api) authorization header", err)
}
var apiKey struct {
models.APIKey
DisabledFlag bool `db:"disabled"`
Headless bool `db:"headless"`
}
// var apiKey models.APIKey
// Defer checking error here to avoid timing attacks to discover valid access keys
err = db.Get(&apiKey, sq.Select("secret_key", "user_id", "disabled", "headless").
From("api_keys").
LeftJoin("users ON users.id = user_id").
Where(sq.Eq{"access_key": accessKey}))
expectedHMAC := signer.BuildRequestHMAC(r, requestBody, apiKey.SecretKey)
if !hmac.Equal(headerHMAC, expectedHMAC) {
return emptyUserData, errors.New("Bad HMAC")
}
if err != nil {
return emptyUserData, backend.WrapError("Unable to retrieve API key data", err)
}
if apiKey.DisabledFlag {
return emptyUserData, backend.DisabledUserError()
}
err = db.Update(sq.Update("api_keys").Set("last_auth", time.Now()).Where(sq.Eq{"access_key": accessKey}))
if err != nil {
logging.Log(r.Context(), "msg", "Failed to update last_auth", "access_key", accessKey, "error", err)
}
return UserData{ID: apiKey.UserID, Headless: apiKey.Headless}, nil
}
// parseAuthorizationHeader parses the authorization header and returns the access key and HMAC
func parseAuthorizationHeader(authorizationStr string) (string, []byte, error) {
if authorizationStr == "" {
return "", []byte{}, errors.New("Missing required Authorization header")
}
split := strings.SplitN(authorizationStr, ":", 2)
if len(split) != 2 {
return "", []byte{}, errors.New("Missing required HMAC signature in Authorization header")
}
accessKey, base64HMAC := split[0], split[1]
headerHMAC, err := base64.StdEncoding.DecodeString(base64HMAC)
if err != nil {
return accessKey, headerHMAC, backend.WrapError("Unable to decode base64 HMAC", err)
}
return accessKey, headerHMAC, nil
}
// checkDateHeader verifies that the passed Date header is valid and within the maxDateDelta of the current time
func checkDateHeader(dateStr string) error {
if dateStr == "" {
return errors.New("Missing required Date header")
}
parsedDate, err := time.Parse(time.RFC1123, dateStr)
if err != nil {
return err
}
if parsedDate.Location().String() != "GMT" {
// RFC7231 specifies the Date header must always be in GMT
// Enforcing this avoids bugs where go silently converts unknown timestamps to
// UTC which may happen in docker containers where tzdata isn't installed
return fmt.Errorf("Date header must be in GMT (got %s)", dateStr)
}
delta := time.Since(parsedDate)
dateIsWithinMaxDelta := (0 < delta && delta < maxDateDelta) || (-maxDateDelta < delta && delta < 0)
if !dateIsWithinMaxDelta {
return fmt.Errorf("Date %s is not within max delta (%s) from current time %s", parsedDate, maxDateDelta, time.Now())
}
return nil
}