-
Notifications
You must be signed in to change notification settings - Fork 351
/
javav2.go
146 lines (130 loc) · 3.7 KB
/
javav2.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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package sig
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"net/http"
"net/url"
"sort"
"strings"
"github.com/treeverse/lakefs/pkg/auth/model"
"github.com/treeverse/lakefs/pkg/gateway/errors"
"github.com/treeverse/lakefs/pkg/logging"
)
// Implements the "signing protocol" as exists at aws-sdk-java @ 1.12.390 (commit 07926f08a70).
// This should never have worked, on any version of S3, but the implementation is there, unmodified since 2015.
// The code is below, for reference
/*
private String calculateStringToSignV2(SignableRequest<?> request) throws SdkClientException {
URI endpoint = request.getEndpoint();
StringBuilder data = new StringBuilder();
data.append("POST")
.append("\n")
.append(getCanonicalizedEndpoint(endpoint)) // <----- bare host, lower-cased
.append("\n")
.append(getCanonicalizedResourcePath(request)) // <----- path relative to the endpoint
.append("\n")
.append(getCanonicalizedQueryString(request.getParameters())); // <----- ordered query string parameters
return data.toString();
}
*/
func canonicalJavaV2String(host string, query url.Values, path string) string {
cs := strings.ToUpper(http.MethodPost) // so weird.
cs += "\n"
cs += host
cs += "\n"
cs += path
cs += "\n"
cs += canonicalJavaV2Query(query)
return cs
}
func canonicalJavaV2Query(q url.Values) string {
escaped := make([][2]string, 0)
for k, vs := range q {
if strings.EqualFold(k, "signature") {
continue
}
escapedKey := url.QueryEscape(k)
for _, v := range vs {
pair := [2]string{escapedKey, url.QueryEscape(v)}
escaped = append(escaped, pair)
}
}
// sort
sort.Slice(escaped, func(i, j int) bool {
return escaped[i][0] < escaped[j][0]
})
// output
out := ""
for i, pair := range escaped {
out += pair[0] + "=" + pair[1]
isLast := i == len(escaped)-1
if !isLast {
out += "&"
}
}
return out
}
type JavaV2Signer struct {
bareDomain string
req *http.Request
sigCtx *JavaV2SignerContext
}
type JavaV2SignerContext struct {
awsAccessKeyID string
signature []byte
}
func NewJavaV2SigAuthenticator(r *http.Request, bareDomain string) *JavaV2Signer {
return &JavaV2Signer{
req: r,
bareDomain: bareDomain,
}
}
func (j *JavaV2SignerContext) GetAccessKeyID() string {
return j.awsAccessKeyID
}
func (j *JavaV2Signer) Parse() (SigContext, error) {
ctx := j.req.Context()
awsAccessKeyID := j.req.URL.Query().Get("AWSAccessKeyId")
if awsAccessKeyID == "" {
return nil, ErrHeaderMalformed
}
signature := j.req.URL.Query().Get("Signature")
if signature == "" {
return nil, ErrHeaderMalformed
}
sig, err := base64.StdEncoding.DecodeString(signature)
if err != nil {
logging.FromContext(ctx).Error("log header does not match v2 structure (isn't proper base64)")
return nil, ErrHeaderMalformed
}
sigMethod := j.req.URL.Query().Get("SignatureMethod")
if sigMethod != "HmacSHA256" {
return nil, ErrHeaderMalformed
}
sigVersion := j.req.URL.Query().Get("SignatureVersion")
if sigVersion != "2" {
return nil, ErrHeaderMalformed
}
sigCtx := &JavaV2SignerContext{
awsAccessKeyID: awsAccessKeyID,
signature: sig,
}
j.sigCtx = sigCtx
return sigCtx, nil
}
func signCanonicalJavaV2String(msg string, signature []byte) []byte {
h := hmac.New(sha256.New, signature)
_, _ = h.Write([]byte(msg))
return h.Sum(nil)
}
func (j *JavaV2Signer) Verify(creds *model.Credential) error {
rawPath := j.req.URL.EscapedPath()
path := buildPath(j.req.Host, j.bareDomain, rawPath)
stringToSign := canonicalJavaV2String(j.req.Host, j.req.URL.Query(), path)
digest := signCanonicalJavaV2String(stringToSign, []byte(creds.SecretAccessKey))
if !Equal(digest, j.sigCtx.signature) {
return errors.ErrSignatureDoesNotMatch
}
return nil
}