/
http.go
107 lines (92 loc) · 3.08 KB
/
http.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
package chef
import (
"bytes"
"crypto"
"crypto/rsa"
"errors"
"fmt"
"net/http"
"path"
"time"
)
// ChefVersion that we pretend to emulate
const ChefVersion = "11.12.0"
// AuthConfig representing a client and a private key used for encryption
type AuthConfig struct {
privateKey *rsa.PrivateKey
clientName string
cryptoHash crypto.Hash
}
// SignRequest modifies headers of an http.Request
func (ac AuthConfig) SignRequest(request *http.Request, cryptoHash crypto.Hash) error {
// sanitize the path for the chef-server
// chef-server doesn't support '//' in the Hash Path.
var endpoint string
if request.URL.Path != "" {
endpoint = path.Clean(request.URL.Path)
request.URL.Path = endpoint
} else {
endpoint = request.URL.Path
}
request.Header.Set("Method", request.Method)
// Since we're not setting the encoded slice limit
// we can safely call out [0]
generatedHash := generateHash(endpoint, cryptoHash)
if generatedHash == nil {
return errors.New("Unsupported crypto hashing algorithm")
}
request.Header.Set("Hashed Path", base64BlockEncode(generatedHash, 0)[0])
request.Header.Set("Accept", "application/json")
request.Header.Set("X-Chef-Version", ChefVersion)
request.Header.Set("X-Ops-Timestamp", time.Now().UTC().Format(time.RFC3339))
request.Header.Set("X-Ops-Userid", ac.clientName)
var xOpsSignCrypto string
switch cryptoHash {
case crypto.MD5:
xOpsSignCrypto = "algorithm=md5;"
case crypto.SHA1:
xOpsSignCrypto = "algorithm=sha1;"
case crypto.SHA224:
xOpsSignCrypto = "algorithm=sha224;"
case crypto.SHA256:
xOpsSignCrypto = "algorithm=sha256;"
case crypto.SHA384:
xOpsSignCrypto = "algorithm=sha384;"
case crypto.SHA512:
xOpsSignCrypto = "algorithm=sha512;"
}
request.Header.Set("X-Ops-Sign", xOpsSignCrypto+"version=1.0")
request.Header.Set("X-Ops-Content-Hash", calcBodyHash(request, cryptoHash))
// To validate the signature it seems to be very particular
var content string
for _, key := range []string{"Method", "Hashed Path", "Accept", "X-Chef-Version", "X-Ops-Timestamp", "X-Ops-Userid", "X-Ops-Sign", "X-Ops-Content-Hash"} {
content += fmt.Sprintf("%s:%s\n", key, request.Header.Get(key))
}
// generate signed string of headers
// Since we've gone through additional validation steps above,
// we shouldn't get an error at this point
signature, _ := generateSignature(ac.privateKey, content, cryptoHash)
// TODO: THIS IS CHEF PROTOCOL SPECIFIC
// Signature is made up of n 60 length chunks
base64sig := base64BlockEncode(signature, 60)
// roll over the auth slice and add the apropriate header
for index, value := range base64sig {
request.Header.Set(fmt.Sprintf("X-Ops-Authorization-%d", index+1), string(value))
}
return nil
}
// modified from goiardi calcBodyHash
func calcBodyHash(r *http.Request, cryptoHash crypto.Hash) string {
var bodyStr string
if r.Body == nil {
bodyStr = ""
} else {
buf := new(bytes.Buffer)
buf.ReadFrom(r.Body)
bodyStr = buf.String()
}
// Since we're not setting the encoded slice limit
// we can safely call out [0]
chkHash := base64BlockEncode(generateHash(bodyStr, cryptoHash), 0)
return chkHash[0]
}