github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/gateway/sig/javav2.go (about) 1 package sig 2 3 import ( 4 "crypto/hmac" 5 "crypto/sha256" 6 "encoding/base64" 7 "net/http" 8 "net/url" 9 "sort" 10 "strings" 11 12 "github.com/treeverse/lakefs/pkg/auth/model" 13 "github.com/treeverse/lakefs/pkg/gateway/errors" 14 "github.com/treeverse/lakefs/pkg/logging" 15 ) 16 17 // Implements the "signing protocol" as exists at aws-sdk-java @ 1.12.390 (commit 07926f08a70). 18 // This should never have worked, on any version of S3, but the implementation is there, unmodified since 2015. 19 // The code is below, for reference 20 /* 21 22 private String calculateStringToSignV2(SignableRequest<?> request) throws SdkClientException { 23 URI endpoint = request.getEndpoint(); 24 25 StringBuilder data = new StringBuilder(); 26 data.append("POST") 27 .append("\n") 28 .append(getCanonicalizedEndpoint(endpoint)) // <----- bare host, lower-cased 29 .append("\n") 30 .append(getCanonicalizedResourcePath(request)) // <----- path relative to the endpoint 31 .append("\n") 32 .append(getCanonicalizedQueryString(request.getParameters())); // <----- ordered query string parameters 33 return data.toString(); 34 } 35 36 */ 37 38 func canonicalJavaV2String(host string, query url.Values, path string) string { 39 cs := strings.ToUpper(http.MethodPost) // so weird. 40 cs += "\n" 41 cs += host 42 cs += "\n" 43 cs += path 44 cs += "\n" 45 cs += canonicalJavaV2Query(query) 46 return cs 47 } 48 49 func canonicalJavaV2Query(q url.Values) string { 50 escaped := make([][2]string, 0) 51 for k, vs := range q { 52 if strings.EqualFold(k, "signature") { 53 continue 54 } 55 escapedKey := url.QueryEscape(k) 56 for _, v := range vs { 57 pair := [2]string{escapedKey, url.QueryEscape(v)} 58 escaped = append(escaped, pair) 59 } 60 } 61 // sort 62 sort.Slice(escaped, func(i, j int) bool { 63 return escaped[i][0] < escaped[j][0] 64 }) 65 // output 66 out := "" 67 for i, pair := range escaped { 68 out += pair[0] + "=" + pair[1] 69 isLast := i == len(escaped)-1 70 if !isLast { 71 out += "&" 72 } 73 } 74 return out 75 } 76 77 type JavaV2Signer struct { 78 bareDomain string 79 req *http.Request 80 sigCtx *JavaV2SignerContext 81 } 82 83 type JavaV2SignerContext struct { 84 awsAccessKeyID string 85 signature []byte 86 } 87 88 func NewJavaV2SigAuthenticator(r *http.Request, bareDomain string) *JavaV2Signer { 89 return &JavaV2Signer{ 90 req: r, 91 bareDomain: bareDomain, 92 } 93 } 94 95 func (j *JavaV2SignerContext) GetAccessKeyID() string { 96 return j.awsAccessKeyID 97 } 98 99 func (j *JavaV2Signer) Parse() (SigContext, error) { 100 ctx := j.req.Context() 101 awsAccessKeyID := j.req.URL.Query().Get("AWSAccessKeyId") 102 if awsAccessKeyID == "" { 103 return nil, ErrHeaderMalformed 104 } 105 signature := j.req.URL.Query().Get("Signature") 106 if signature == "" { 107 return nil, ErrHeaderMalformed 108 } 109 sig, err := base64.StdEncoding.DecodeString(signature) 110 if err != nil { 111 logging.FromContext(ctx).Error("log header does not match v2 structure (isn't proper base64)") 112 return nil, ErrHeaderMalformed 113 } 114 sigMethod := j.req.URL.Query().Get("SignatureMethod") 115 if sigMethod != "HmacSHA256" { 116 return nil, ErrHeaderMalformed 117 } 118 sigVersion := j.req.URL.Query().Get("SignatureVersion") 119 if sigVersion != "2" { 120 return nil, ErrHeaderMalformed 121 } 122 sigCtx := &JavaV2SignerContext{ 123 awsAccessKeyID: awsAccessKeyID, 124 signature: sig, 125 } 126 j.sigCtx = sigCtx 127 return sigCtx, nil 128 } 129 130 func signCanonicalJavaV2String(msg string, signature []byte) []byte { 131 h := hmac.New(sha256.New, signature) 132 _, _ = h.Write([]byte(msg)) 133 return h.Sum(nil) 134 } 135 136 func (j *JavaV2Signer) Verify(creds *model.Credential) error { 137 rawPath := j.req.URL.EscapedPath() 138 139 path := buildPath(j.req.Host, j.bareDomain, rawPath) 140 stringToSign := canonicalJavaV2String(j.req.Host, j.req.URL.Query(), path) 141 digest := signCanonicalJavaV2String(stringToSign, []byte(creds.SecretAccessKey)) 142 if !Equal(digest, j.sigCtx.signature) { 143 return errors.ErrSignatureDoesNotMatch 144 } 145 return nil 146 }