http_signer.go raw
1 // Copyright (c) 2016, 2018, 2025, Oracle and/or its affiliates. All rights reserved.
2 // This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
3
4 package common
5
6 import (
7 "bytes"
8 "crypto"
9 "crypto/rand"
10 "crypto/rsa"
11 "crypto/sha256"
12 "encoding/base64"
13 "fmt"
14 "io"
15 "io/ioutil"
16 "net/http"
17 "strings"
18 )
19
20 // HTTPRequestSigner the interface to sign a request
21 type HTTPRequestSigner interface {
22 Sign(r *http.Request) error
23 }
24
25 // KeyProvider interface that wraps information about the key's account owner
26 type KeyProvider interface {
27 PrivateRSAKey() (*rsa.PrivateKey, error)
28 KeyID() (string, error)
29 }
30
31 const signerVersion = "1"
32
33 // SignerBodyHashPredicate a function that allows to disable/enable body hashing
34 // of requests and headers associated with body content
35 type SignerBodyHashPredicate func(r *http.Request) bool
36
37 // ociRequestSigner implements the http-signatures-draft spec
38 // as described in https://tools.ietf.org/html/draft-cavage-http-signatures-08
39 type ociRequestSigner struct {
40 KeyProvider KeyProvider
41 GenericHeaders []string
42 BodyHeaders []string
43 ShouldHashBody SignerBodyHashPredicate
44 }
45
46 var (
47 defaultGenericHeaders = []string{"date", "(request-target)", "host"}
48 defaultBodyHeaders = []string{"content-length", "content-type", "x-content-sha256"}
49 defaultBodyHashPredicate = func(r *http.Request) bool {
50 return r.Method == http.MethodPost || r.Method == http.MethodPut || r.Method == http.MethodPatch
51 }
52 )
53
54 // DefaultGenericHeaders list of default generic headers that is used in signing
55 func DefaultGenericHeaders() []string {
56 return makeACopy(defaultGenericHeaders)
57 }
58
59 // DefaultBodyHeaders list of default body headers that is used in signing
60 func DefaultBodyHeaders() []string {
61 return makeACopy(defaultBodyHeaders)
62 }
63
64 // DefaultRequestSigner creates a signer with default parameters.
65 func DefaultRequestSigner(provider KeyProvider) HTTPRequestSigner {
66 return RequestSigner(provider, defaultGenericHeaders, defaultBodyHeaders)
67 }
68
69 // RequestSignerExcludeBody creates a signer without hash the body.
70 func RequestSignerExcludeBody(provider KeyProvider) HTTPRequestSigner {
71 bodyHashPredicate := func(r *http.Request) bool {
72 // week request signer will not hash the body
73 return false
74 }
75 return RequestSignerWithBodyHashingPredicate(provider, defaultGenericHeaders, defaultBodyHeaders, bodyHashPredicate)
76 }
77
78 // NewSignerFromOCIRequestSigner creates a copy of the request signer and attaches the new SignerBodyHashPredicate
79 // returns an error if the passed signer is not of type ociRequestSigner
80 func NewSignerFromOCIRequestSigner(oldSigner HTTPRequestSigner, predicate SignerBodyHashPredicate) (HTTPRequestSigner, error) {
81 if oldS, ok := oldSigner.(ociRequestSigner); ok {
82 s := ociRequestSigner{
83 KeyProvider: oldS.KeyProvider,
84 GenericHeaders: oldS.GenericHeaders,
85 BodyHeaders: oldS.BodyHeaders,
86 ShouldHashBody: predicate,
87 }
88 return s, nil
89
90 }
91 return nil, fmt.Errorf("can not create a signer, input signer needs to be of type ociRequestSigner")
92 }
93
94 // RequestSigner creates a signer that utilizes the specified headers for signing
95 // and the default predicate for using the body of the request as part of the signature
96 func RequestSigner(provider KeyProvider, genericHeaders, bodyHeaders []string) HTTPRequestSigner {
97 return ociRequestSigner{
98 KeyProvider: provider,
99 GenericHeaders: genericHeaders,
100 BodyHeaders: bodyHeaders,
101 ShouldHashBody: defaultBodyHashPredicate}
102 }
103
104 // RequestSignerWithBodyHashingPredicate creates a signer that utilizes the specified headers for signing, as well as a predicate for using
105 // the body of the request and bodyHeaders parameter as part of the signature
106 func RequestSignerWithBodyHashingPredicate(provider KeyProvider, genericHeaders, bodyHeaders []string, shouldHashBody SignerBodyHashPredicate) HTTPRequestSigner {
107 return ociRequestSigner{
108 KeyProvider: provider,
109 GenericHeaders: genericHeaders,
110 BodyHeaders: bodyHeaders,
111 ShouldHashBody: shouldHashBody}
112 }
113
114 func (signer ociRequestSigner) getSigningHeaders(r *http.Request) []string {
115 var result []string
116 result = append(result, signer.GenericHeaders...)
117
118 if signer.ShouldHashBody(r) {
119 result = append(result, signer.BodyHeaders...)
120 }
121
122 return result
123 }
124
125 func (signer ociRequestSigner) getSigningString(request *http.Request) string {
126 signingHeaders := signer.getSigningHeaders(request)
127 signingParts := make([]string, len(signingHeaders))
128 for i, part := range signingHeaders {
129 var value string
130 part = strings.ToLower(part)
131 switch part {
132 case "(request-target)":
133 value = getRequestTarget(request)
134 case "host":
135 value = request.URL.Host
136 if len(value) == 0 {
137 value = request.Host
138 }
139 default:
140 value = request.Header.Get(part)
141 }
142 signingParts[i] = fmt.Sprintf("%s: %s", part, value)
143 }
144
145 signingString := strings.Join(signingParts, "\n")
146 return signingString
147
148 }
149
150 func getRequestTarget(request *http.Request) string {
151 lowercaseMethod := strings.ToLower(request.Method)
152 return fmt.Sprintf("%s %s", lowercaseMethod, request.URL.RequestURI())
153 }
154
155 func calculateHashOfBody(request *http.Request) (err error) {
156 var hash string
157 hash, err = GetBodyHash(request)
158 if err != nil {
159 return
160 }
161 request.Header.Set(requestHeaderXContentSHA256, hash)
162 return
163 }
164
165 // drainBody reads all of b to memory and then returns two equivalent
166 // ReadClosers yielding the same bytes.
167 //
168 // It returns an error if the initial slurp of all bytes fails. It does not attempt
169 // to make the returned ReadClosers have identical error-matching behavior.
170 func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
171 if b == http.NoBody {
172 // No copying needed. Preserve the magic sentinel meaning of NoBody.
173 return http.NoBody, http.NoBody, nil
174 }
175 var buf bytes.Buffer
176 if _, err = buf.ReadFrom(b); err != nil {
177 return nil, b, err
178 }
179 if err = b.Close(); err != nil {
180 return nil, b, err
181 }
182 return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil
183 }
184
185 func hashAndEncode(data []byte) string {
186 hashedContent := sha256.Sum256(data)
187 hash := base64.StdEncoding.EncodeToString(hashedContent[:])
188 return hash
189 }
190
191 // GetBodyHash creates a base64 string from the hash of body the request
192 func GetBodyHash(request *http.Request) (hashString string, err error) {
193 if request.Body == nil {
194 request.ContentLength = 0
195 request.Header.Set(requestHeaderContentLength, fmt.Sprintf("%v", request.ContentLength))
196 return hashAndEncode([]byte("")), nil
197 }
198
199 var data []byte
200 var bReader io.Reader
201 bReader, request.Body, err = drainBody(request.Body)
202 if err != nil {
203 return "", fmt.Errorf("can not read body of request while calculating body hash: %s", err.Error())
204 }
205
206 data, err = ioutil.ReadAll(bReader)
207 if err != nil {
208 return "", fmt.Errorf("can not read body of request while calculating body hash: %s", err.Error())
209 }
210
211 // Since the request can be coming from a binary body. Make an attempt to set the body length
212 request.ContentLength = int64(len(data))
213 request.Header.Set(requestHeaderContentLength, fmt.Sprintf("%v", request.ContentLength))
214
215 hashString = hashAndEncode(data)
216 return
217 }
218
219 func (signer ociRequestSigner) computeSignature(request *http.Request) (signature string, err error) {
220 signingString := signer.getSigningString(request)
221 hasher := sha256.New()
222 hasher.Write([]byte(signingString))
223 hashed := hasher.Sum(nil)
224
225 privateKey, err := signer.KeyProvider.PrivateRSAKey()
226 if err != nil {
227 return
228 }
229
230 var unencodedSig []byte
231 unencodedSig, e := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed)
232 if e != nil {
233 err = fmt.Errorf("can not compute signature while signing the request %s: ", e.Error())
234 return
235 }
236
237 signature = base64.StdEncoding.EncodeToString(unencodedSig)
238 return
239 }
240
241 // Sign signs the http request, by inspecting the necessary headers. Once signed
242 // the request will have the proper 'Authorization' header set, otherwise
243 // and error is returned
244 func (signer ociRequestSigner) Sign(request *http.Request) (err error) {
245 if signer.ShouldHashBody(request) {
246 err = calculateHashOfBody(request)
247 if err != nil {
248 return
249 }
250 }
251
252 var signature string
253 if signature, err = signer.computeSignature(request); err != nil {
254 return
255 }
256
257 signingHeaders := strings.Join(signer.getSigningHeaders(request), " ")
258
259 var keyID string
260 if keyID, err = signer.KeyProvider.KeyID(); err != nil {
261 return
262 }
263
264 authValue := fmt.Sprintf("Signature version=\"%s\",headers=\"%s\",keyId=\"%s\",algorithm=\"rsa-sha256\",signature=\"%s\"",
265 signerVersion, signingHeaders, keyID, signature)
266
267 request.Header.Set(requestHeaderAuthorization, authValue)
268
269 return
270 }
271