/
context_signature_plugin.go
156 lines (134 loc) · 4.75 KB
/
context_signature_plugin.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
147
148
149
150
151
152
153
154
155
156
package osb
import (
"context"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
//"encoding/json"
"encoding/pem"
"fmt"
"github.com/Peripli/service-manager/pkg/log"
"github.com/Peripli/service-manager/pkg/web"
)
const (
ContextSignaturePluginName = "ContextSignaturePlugin"
ServiceInstanceIDFieldName = "service_instance_id"
)
type ContextSignaturePlugin struct {
contextSigner *ContextSigner
}
type ContextSigner struct {
ContextPrivateKey string
rsaPrivateKey *rsa.PrivateKey
}
func NewCtxSignaturePlugin(contextSigner *ContextSigner) *ContextSignaturePlugin {
return &ContextSignaturePlugin{
contextSigner: contextSigner,
}
}
func (s *ContextSignaturePlugin) Name() string {
return ContextSignaturePluginName
}
func (s *ContextSignaturePlugin) Provision(req *web.Request, next web.Handler) (*web.Response, error) {
return s.signContext(req, next)
}
func (s *ContextSignaturePlugin) Bind(req *web.Request, next web.Handler) (*web.Response, error) {
return s.signContext(req, next)
}
func (s *ContextSignaturePlugin) UpdateService(req *web.Request, next web.Handler) (*web.Response, error) {
return s.signContext(req, next)
}
func (s *ContextSignaturePlugin) signContext(req *web.Request, next web.Handler) (*web.Response, error) {
//in case the private key is not provided we continue without adding the signature. this is useful in case we want to toggle off the feature
if s.contextSigner.ContextPrivateKey == "" {
log.C(req.Context()).Debugf("context private key not found. context signature can not be calculated")
return next.Handle(req)
}
//unmarshal and marshal the request body so the fields within the context will be ordered lexicographically, and to get rid of redundant spaces\drop-line\tabs
var reqBodyMap map[string]interface{}
err := json.Unmarshal(req.Body, &reqBodyMap)
if err != nil {
log.C(req.Context()).Errorf("failed to unmarshal context: %v", err)
return next.Handle(req)
}
if _, found := reqBodyMap["context"]; !found {
errorMsg := "context not found on request body"
log.C(req.Context()).Error(errorMsg)
return next.Handle(req)
}
contextMap := reqBodyMap["context"].(map[string]interface{})
if instanceID, ok := req.PathParams[InstanceIDPathParam]; ok {
contextMap[ServiceInstanceIDFieldName] = instanceID
}
err = s.contextSigner.Sign(req.Context(), contextMap)
if err != nil {
log.C(req.Context()).Errorf("failed to sign request context: %v", err)
return next.Handle(req)
}
reqBody, err := json.Marshal(reqBodyMap)
if err != nil {
log.C(req.Context()).Errorf("failed to marshal request body: %v", err)
return next.Handle(req)
}
req.Body = reqBody
return next.Handle(req)
}
func (cs *ContextSigner) Sign(ctx context.Context, contextMap map[string]interface{}) error {
if cs.ContextPrivateKey == "" {
errorMsg := "context rsa private key is missing. context signature can not be calculated"
log.C(ctx).Errorf(errorMsg)
return fmt.Errorf(errorMsg)
}
ctxByte, err := json.Marshal(contextMap)
if err != nil {
log.C(ctx).Errorf("failed to marshal context: %v", err)
return err
}
//on the first time the sign function is executed we should parse the rsa private key and keep it for next executions
if cs.rsaPrivateKey == nil {
cs.rsaPrivateKey, err = cs.parseRsaPrivateKey(ctx, cs.ContextPrivateKey)
if err != nil {
log.C(ctx).Errorf("failed to parse rsa private key: %v", err)
return err
}
}
signedCtx, err := cs.calculateSignature(ctx, string(ctxByte), cs.rsaPrivateKey)
if err != nil {
log.C(ctx).Errorf("failed to calculate the context signature: %v", err)
return err
}
contextMap["signature"] = signedCtx
return nil
}
func (cs *ContextSigner) parseRsaPrivateKey(ctx context.Context, rsaPrivateKey string) (*rsa.PrivateKey, error) {
key, err := base64.StdEncoding.DecodeString(rsaPrivateKey)
if err != nil {
log.C(ctx).Errorf("failed to base64 decode rsa private key: %v", err)
return nil, err
}
block, _ := pem.Decode(key)
if block == nil {
log.C(ctx).Error("failed to pem decode rsa private key")
return nil, fmt.Errorf("failed to pem decode context rsa private key")
}
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
log.C(ctx).Errorf("fail to parse rsa key, %s", err.Error())
return nil, err
}
return privateKey, nil
}
func (cs *ContextSigner) calculateSignature(ctx context.Context, ctxStr string, rsaPrivateKey *rsa.PrivateKey) (string, error) {
log.C(ctx).Debugf("creating signature for ctx: %s", ctxStr)
hashedCtx := sha256.Sum256([]byte(ctxStr))
signature, err := rsa.SignPKCS1v15(rand.Reader, rsaPrivateKey, crypto.SHA256, hashedCtx[:])
if err != nil {
log.C(ctx).Errorf("failed to encrypt context %v", err)
return "", err
}
return base64.StdEncoding.EncodeToString(signature), nil
}