Skip to content

Commit

Permalink
Adds http signatures to callbacks
Browse files Browse the repository at this point in the history
Resolves openfaas#48

Signed-off-by: Edward Wilde <[email protected]>
  • Loading branch information
ewilde committed Feb 24, 2019
1 parent 7cdf2cf commit e1f0304
Show file tree
Hide file tree
Showing 71 changed files with 7,296 additions and 1 deletion.
25 changes: 24 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,7 @@
[[constraint]]
name = "github.com/openfaas/faas-provider"
version = "0.7.0"

[[constraint]]
name = "github.com/go-fed/httpsig"
version = "0.1.0"
91 changes: 91 additions & 0 deletions http/signatures.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package http

import (
"bytes"
"crypto"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"time"

"github.com/go-fed/httpsig"
)


var privateKey *rsa.PrivateKey
func init() {
var err error
var signingKey []byte

if _, err := os.Stat("/run/secrets/http-signing-private-key"); os.IsNotExist(err) {
log.Println("Warning callback messages will not be signed missing private key: /run/secrets/http-signing-private-key")
return // backwards compatibility, if no signing key given we won't sign messages
}

signingKey, err = ioutil.ReadFile("/run/secrets/http-signing-private-key")
if err != nil {
panic(err)
}

privateKey, err = loadPrivateKey(signingKey)
if err != nil {
panic(err)
}
}

func SignMessage(request *http.Request) error {
if privateKey == nil {
return nil // backwards compatibility, if no signing key given we won't sign messages
}

return signMessageWithKey(privateKey, request)
}

func signMessageWithKey(privateKey crypto.PrivateKey, request *http.Request) error {
if _, ok := request.Header["Date"]; !ok {
request.Header["Date"] = []string{time.Now().Format(http.TimeFormat)}
}

if _, ok := request.Header["Digest"]; !ok {
body, err := ioutil.ReadAll(request.Body)
if err != nil {
return fmt.Errorf("error reading body. %v", err)
}

// And now set a new body, which will simulate the same data we read:
request.Body = ioutil.NopCloser(bytes.NewBuffer(body))

request.Header["Digest"] = []string{fmt.Sprintf("%x", sha256.Sum256(body))}
}


// The "Date" and "Digest" headers must already be set on r, as well as r.URL.
headersToSign := []string{httpsig.RequestTarget, "date", "content-type", "digest", "content-length"}
preferences := []httpsig.Algorithm{httpsig.RSA_SHA256}

signer, _, err := httpsig.NewSigner(preferences, headersToSign, httpsig.Signature)
if err != nil {
return fmt.Errorf("error creating request signer. %v", err)
}

if err:= signer.SignRequest(privateKey, "callback", request); err != nil {
return fmt.Errorf("error siging request. %v", err)
}

return nil
}

func loadPrivateKey(keyData []byte) (*rsa.PrivateKey, error) {
pem, _ := pem.Decode(keyData)
if pem.Type != "RSA PRIVATE KEY" {
return nil, fmt.Errorf("RSA private key is of the wrong type: %s", pem.Type)
}

return x509.ParsePKCS1PrivateKey(pem.Bytes)
}
121 changes: 121 additions & 0 deletions http/signatures_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package http

import (
"bytes"
"crypto/rsa"
"fmt"
"net/http"
"testing"
)

var(
testRSAKey *rsa.PrivateKey
)

func init() {
var err error
testRSAKey , err = loadPrivateKey([]byte(testPrivateKey))
if err != nil {
panic(err)
}
}

func TestSignMessage(t *testing.T) {
type args struct {
request *http.Request
}
tests := []struct {
name string
args args
wantErr bool
eval func(r *http.Request) error
}{
{
name: "request is missing date and digest header",
args: struct{ request *http.Request
}{
request: createRequest("POST", "http://callback.com", `{ "name": "foo"}`),
},
wantErr: false,
eval: func(r *http.Request) error {
signature := r.Header["Signature"]
digest := r.Header["Digest"]
if len (signature) == 0 {
return fmt.Errorf("signature not present")
}

if len (digest) == 0 {
return fmt.Errorf("digest not present")
}

return nil
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := signMessageWithKey(testRSAKey, tt.args.request); (err != nil) != tt.wantErr {
t.Errorf("SignMessage() error = %v, wantErr %v", err, tt.wantErr)
}

if err:= tt.eval(tt.args.request); err != nil {
t.Errorf("error validating signature. %v", err)
}
})
}
}

func TestLoadPrivateKey(t *testing.T) {
key, err := loadPrivateKey([]byte(testPrivateKey))
if err != nil {
t.Errorf("error loading private key from PEM. %v", err)
}

publicKey := key.Public()
if publicKey == nil {
t.Errorf("error creating public key from private")
}
}

func createRequest(method string, url string, body string) *http.Request {
r, _ := http.NewRequest(method, url, bytes.NewBuffer([]byte(body)))
return r
}

const testPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA7pEUKQ28pI5N3g/zG6OJ100N/DV2Q8Ob+gzRjd7HjXgVgZyj
S3nA8FAYrxTLSihcIhXuQrYxyk2vp6YMNmSBfOptkdmj4UgLYskfeqEt8JjS6ExB
xSWEDgr1IXOPPDP61on8F65/ZYGnp2JF2wHYk0OeD4ppNUV+mIHj/wXf7VLHGflw
FQH/+mfUn+tVQRgX7hTadcYmGJ+1XP0py4kUgJDHfw8eBsFurHWr2mXu3BdraSKK
f1G9i+SifmOUUul6mBONmlvzQdKtDCr48o1HQndRHcMWjKhlBhKz4qrmqku8oGBh
6iHhGVVYf8D3mU1nzyjH4rOUXZwzj+SaqgGkvQIDAQABAoIBAA/HuuyoQvUjkJUC
uxL181UxfJ5VLvh2hOe6V1YqCUsbSVjkcoy4hgGfWrKiDnxeRRHgH318LbjzAI53
VlF2vnXzz8GZAtUQ/efP4+wpoy9J/JFvEd6nh5+iK+rFiRDzY0EzqWAro7OkKuHQ
h0xgR+Id0+O1RRZH/YK6/MjOq6w378/sXeKGh5AYRLP1wYdH7zLtDa/jBzI6VX3U
/TO68hOdAGkNOzub/Bh8DFSI/n9s2Qazitw3LkT9uPH6lLVizuLpUkKqyWtJSsk4
cs32Nvc/7Vlp3VwvJMM2tHt3Wp8kRsiYAU7gmFMSQOjx1D6Ivy4i/pXUoL65Cb8f
ZXJM9GUCgYEA+AkKmsDMWtxm+GGoE1vEAwBoP7ZjVw8uxurrn7qoCRkK2ob4c0mc
Yy47IJQRKPgVqh+Mz77PH3j8wDPUqwF+iXpCZ8Ywqpa8+gVFsGaSdq9ccoLRAvhL
Dv8baJZTvOZ3HpvLrNlUwNaWKVPVfJcIyhcGGUzIPWr/DkgeEaJbDisCgYEA9joz
hlIeQRn/ZVqFyFwtX3aMB4drXd+3see8NDe6YB06c/bAXzfJllxh03ataVP0Bx27
qw7QTd462985xhCLs2vf15Uny2j2CsBP3/AENWlrRBltVgM9GHi8YECZLjlLnavV
u1US22xYjJVn9KOaywVPG27QFOeGQ5DRMMMBjLcCgYB2G6AQFrR4o7Don1/wb+cD
YuNBS3E8WH92uxCeC7zOMD2J13FhPHEajT4cgkU1lASE8OcVWY+5NdxtDYE8OMbv
YedTEP3FjWfJSk9n8z/VAiXsZTxvxJnBN1ruz35qWffo/FjdItQHy2bPoRrsa+ME
kDgYPgavsE5pl4+x5/Sh5wKBgQC6KMS94CYpmEtEyMzu7+oKC+nQ42yke7k5k3fy
jtOlSVPhSwSkuNv7h3sa0tZHFQchvjQeH0QUK1ma1MmsWuQUUhHZv1Zn7sJe3IlH
SQMVks9bnFHSvv4ZG2do6k4l6YGnArzENozcQq0sFOWUy4EDz87AceZ2d5lGSh4u
3LC2PwKBgBMHoYUY2SY+CWSaL1oRKi6t1ZF6phNntvWZF/NHivNmdxFUOB7XIG3L
lHUCArXs2qRSJ/EPCB/GtQEQPmrij0YIarunD9a5/i5Qs5XaQvt5mwLxN9sIH1Cp
Y4xKvUQfpXtlFF6qYrK6Ig1gQQA8jfW135ibnmFdy22fhhKrGX0R
-----END RSA PRIVATE KEY-----`

const testPublicKey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7pEUKQ28pI5N3g/zG6OJ
100N/DV2Q8Ob+gzRjd7HjXgVgZyjS3nA8FAYrxTLSihcIhXuQrYxyk2vp6YMNmSB
fOptkdmj4UgLYskfeqEt8JjS6ExBxSWEDgr1IXOPPDP61on8F65/ZYGnp2JF2wHY
k0OeD4ppNUV+mIHj/wXf7VLHGflwFQH/+mfUn+tVQRgX7hTadcYmGJ+1XP0py4kU
gJDHfw8eBsFurHWr2mXu3BdraSKKf1G9i+SifmOUUul6mBONmlvzQdKtDCr48o1H
QndRHcMWjKhlBhKz4qrmqku8oGBh6iHhGVVYf8D3mU1nzyjH4rOUXZwzj+SaqgGk
vQIDAQAB
-----END PUBLIC KEY-----`
29 changes: 29 additions & 0 deletions vendor/github.com/go-fed/httpsig/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 85 additions & 0 deletions vendor/github.com/go-fed/httpsig/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit e1f0304

Please sign in to comment.