Use asym key to sign webhooks (#916)

* use async key pair for webhooks

* fix tests

* fix linter

* improve code

* add key pair to database

* undo some changes

* more undo

* improve docs

* add api-endpoint

* add signaturne api endpoint

* fix error

* fix linting and test

* fix lint

* add test

* migration 006

* no need for migration

* replace httsign lib

* fix lint

Co-authored-by: 6543 <6543@obermui.de>
This commit is contained in:
Anbraten
2022-06-01 20:06:27 +02:00
committed by GitHub
parent a2ca657631
commit cc30db44ac
46 changed files with 1763 additions and 633 deletions

191
vendor/github.com/go-ap/httpsig/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "[]" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

54
vendor/github.com/go-ap/httpsig/Makefile generated vendored Normal file
View File

@@ -0,0 +1,54 @@
GOPATH := $(shell go env GOPATH)
all: build
getdeps:
@echo "Installing golint" && go get -u golang.org/x/lint/golint
@echo "Installing gocyclo" && go get -u github.com/fzipp/gocyclo
@echo "Installing deadcode" && go get -u github.com/remyoudompheng/go-misc/deadcode
@echo "Installing misspell" && go get -u github.com/client9/misspell/cmd/misspell
@echo "Installing ineffassign" && go get -u github.com/gordonklaus/ineffassign
verifiers: vet fmt lint cyclo spelling static deadcode
vet:
@echo "Running $@"
@go vet -atomic -bool -copylocks -nilfunc -printf -rangeloops -unreachable -unsafeptr -unusedresult ./...
fmt:
@echo "Running $@"
@gofmt -d .
lint:
@echo "Running $@"
@${GOPATH}/bin/golint -set_exit_status $(shell go list ./...)
ineffassign:
@echo "Running $@"
@${GOPATH}/bin/ineffassign .
cyclo:
@echo "Running $@"
@${GOPATH}/bin/gocyclo -over 100 .
deadcode:
@echo "Running $@"
@${GOPATH}/bin/deadcode -test $(shell go list ./...) || true
spelling:
@echo "Running $@"
@${GOPATH}/bin/misspell -i monitord -error `find .`
static:
@echo "Running $@"
go run honnef.co/go/tools/cmd/staticcheck -- ./...
check: test
test: verifiers build
go test -v ./...
testrace: verifiers build
go test -v -race ./...
build:
go build -v ./...

138
vendor/github.com/go-ap/httpsig/README.md generated vendored Normal file
View File

@@ -0,0 +1,138 @@
# HTTPSIG for Go
This library implements HTTP request signature generation and verification based on
the RFC draft specification https://tools.ietf.org/html/draft-cavage-http-signatures-12.
The library strives be compatible with the popular python library of the same
name: https://github.com/ahknight/httpsig
## Installing
```
go get gopkg.in/spacemonkeygo/httpsig.v0
```
## Signing Requests
Signing requests is done by constructing a new `Signer`. The key id, key,
algorithm, and what headers to sign are required.
For example to construct a `Signer` with key id `"foo"`, using an RSA private
key, for the rsa-sha256 algorithm, with the default header set, you can do:
```go
var key *rsa.PrivateKey = ...
signer := httpsig.NewSigner("foo", key, httpsig.RSASHA256, nil)
```
There are helper functions for specific algorithms that are less verbose and
provide more type safety (the key paramater need not be of type `interface{}`
because the type required for the algorithm is known).
```go
var key *rsa.PrivateKey = ...
signer := httpsig.NewRSASHA256Signer("foo", key, nil)
```
By default, if no headers are passed to `NewSigner` (or the helpers), the
`(request-target)` pseudo-header and `Date` header are signed.
To sign requests, call the `Sign()` method. The method signs the request and
adds an `Authorization` header containing the signature parameters.
```go
err = signer.Sign(req)
if err != nil {
...
}
fmt.Println("AUTHORIZATION:", req.Header.Get('Authorization'))
...
AUTHORIZATION: Signature: keyId="foo",algorithm="sha-256",signature="..."
```
## Verifying Requests
Verifying requests is done by constructing a new `Verifier`. The verifier
requires a KeyGetter implementation to look up keys based on `keyId`'s
retrieved from signature parameters.
```go
var getter httpsig.KeyGetter = ....
verifier := httpsig.NewVerifier(getter)
```
A request can be verified by calling the `Verify()` method:
```go
err = verifier.Verify(req)
```
By default, the verifier only requires the `Date` header to be included
in the signature. The set of required headers be changed using the
`SetRequiredHeaders()` method to enforce stricter requirements.
```go
verifier.SetRequiredHeaders([]string{"(request-target)", "host", "date"})
```
Requests that don't include the full set of required headers in the `headers`
signature parameter (either implicitly or explicitly) will fail verification.
**Note that required headers are simply a specification for which headers must
be included in the signature, and does not enforce header presence in requests.
It is up to callers to validate header contents (or the lack thereof).**
A simple in-memory key store is provided by the library and can be constructed
with the `NewMemoryKeyStore()` function. Keys can be added using the SetKey
method:
```go
keystore := NewMemoryKeyStore()
var rsa_key *rsa.PublicKey = ...
keystore.SetKey("foo", rsa_key)
var hmac_key []byte = ...
keystore.SetKey("foo", hmac_key)
```
## Handler
A convenience function is provided that wraps an `http.Handler` and verifies
incoming request signatures before passing them down to the wrapped handler.
If requires a verifier and optionally a realm (for constructing the
`WWW-Authenticate` header).
```go
var handler http.Handler = ...
var verifier *httpsig.Verifier = ...
wrapped := httpsig.RequireSignature(handler, verifier, "example.com")
```
If signature validation fails, a `401` is returned along with a
`WWW-Authenticate` header containing a `Signature` challenge with optional
`realm` and `headers` parameters.
## Supported algorithms
- rsa-sha1 (using PKCS1v15)
- rsa-sha256 (using PKCS1v15)
- hmac-sha256
- ed25519
### License
Copyright (C) 2017 Space Monkey, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

140
vendor/github.com/go-ap/httpsig/common.go generated vendored Normal file
View File

@@ -0,0 +1,140 @@
// Copyright (C) 2017 Space Monkey, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpsig
import (
ed "crypto/ed25519"
"crypto/rand"
"crypto/rsa"
"fmt"
"io"
"net/http"
"strings"
"time"
)
var (
// Rand is a hookable reader used as a random byte source.
Rand io.Reader = rand.Reader
)
// requestPath returns the :path pseudo header according to the HTTP/2 spec.
func requestPath(req *http.Request) string {
path := req.URL.Path
if path == "" {
path = "/"
}
if req.URL.RawQuery != "" {
path += "?" + req.URL.RawQuery
}
return path
}
// BuildSignatureString constructs a signature string following section 2.3
func BuildSignatureString(req *http.Request, headers []string, created, expires time.Time) (string, error) {
if len(headers) == 0 {
headers = []string{"(created)"}
}
values := make([]string, 0, len(headers))
for _, h := range headers {
switch h {
case "(request-target)":
values = append(values, fmt.Sprintf("%s: %s %s",
h, strings.ToLower(req.Method), requestPath(req)))
case "(created)":
values = append(values, fmt.Sprintf("%s: %d", h, created.Unix()))
case "(expires)":
values = append(values, fmt.Sprintf("%s: %d", h, expires.Unix()))
case "host":
values = append(values, fmt.Sprintf("%s: %s", h, req.Host))
case "date":
if req.Header.Get(h) == "" {
req.Header.Set(h, time.Now().UTC().Format(http.TimeFormat))
}
values = append(values, fmt.Sprintf("%s: %s", h, req.Header.Get(h)))
default:
vs, found := req.Header[http.CanonicalHeaderKey(h)]
if !found {
return "", fmt.Errorf("expected %s to exists", h)
}
for _, v := range vs {
values = append(values, fmt.Sprintf("%s: %s", h, strings.TrimSpace(v)))
}
}
}
return strings.Join(values, "\n"), nil
}
// BuildSignatureData is a convenience wrapper around BuildSignatureString that
// returns []byte instead of a string.
func BuildSignatureData(req *http.Request, headers []string, created, expires time.Time) ([]byte, error) {
s, err := BuildSignatureString(req, headers, created, expires)
return []byte(s), err
}
func toRSAPrivateKey(key interface{}) *rsa.PrivateKey {
switch k := key.(type) {
case *rsa.PrivateKey:
return k
default:
return nil
}
}
func toRSAPublicKey(key interface{}) *rsa.PublicKey {
switch k := key.(type) {
case *rsa.PublicKey:
return k
case *rsa.PrivateKey:
return &k.PublicKey
default:
return nil
}
}
func toHMACKey(key interface{}) []byte {
switch k := key.(type) {
case []byte:
return k
default:
return nil
}
}
func toEd25519PrivateKey(key interface{}) ed.PrivateKey {
switch k := key.(type) {
case ed.PrivateKey:
return k
default:
return nil
}
}
func toEd25519PublicKey(key interface{}) ed.PublicKey {
switch k := key.(type) {
case ed.PublicKey:
return k
case ed.PrivateKey:
return k.Public().(ed.PublicKey)
default:
return nil
}
}
func unsupportedAlgorithm(a Algorithm) error {
return fmt.Errorf("key does not support algorithm %q", a.Name())
}

58
vendor/github.com/go-ap/httpsig/ed25519.go generated vendored Normal file
View File

@@ -0,0 +1,58 @@
package httpsig
import (
ed "crypto/ed25519"
"fmt"
)
// Ed25519 implements Ed25519 Algorithm
var Ed25519 Algorithm = ed25519{}
type ed25519 struct{}
func (ed25519) Name() string {
return "ed25519"
}
func (a ed25519) Sign(key interface{}, data []byte) ([]byte, error) {
k := toEd25519PrivateKey(key)
if k == nil {
return nil, unsupportedAlgorithm(a)
}
return Ed25519Sign(k, data)
}
func (a ed25519) Verify(key interface{}, data, sig []byte) error {
k := toHMACKey(key)
if k == nil {
return unsupportedAlgorithm(a)
}
return Ed25519Verify(k, data, sig)
}
// Ed25519Verify reports whether sig is a valid signature of message by publicKey.
func Ed25519Verify(key interface{}, message, sig []byte) error {
k, ok := key.(ed.PublicKey)
if !ok {
return fmt.Errorf("key must be an instance of crypto/ed25519.PublicKey")
}
if len(k) != ed.PublicKeySize {
return fmt.Errorf("public key has the wrong size")
}
if !ed.Verify(k, message, sig) {
return fmt.Errorf("signature verification failed")
}
return nil
}
// Ed25519Sign signs the message with privateKey and returns a signature.
func Ed25519Sign(key interface{}, message []byte) ([]byte, error) {
k, ok := key.(ed.PrivateKey)
if !ok {
return nil, fmt.Errorf("key must be an instance of crypto/ed25519.PrivateKey")
}
if len(k) != ed.PrivateKeySize {
return nil, fmt.Errorf("private key has the wrong size")
}
return ed.Sign(k, message), nil
}

9
vendor/github.com/go-ap/httpsig/go.mod generated vendored Normal file
View File

@@ -0,0 +1,9 @@
module github.com/go-ap/httpsig
go 1.14
require (
github.com/kr/pretty v0.1.0 // indirect
github.com/stretchr/testify v1.5.1
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)

18
vendor/github.com/go-ap/httpsig/go.sum generated vendored Normal file
View File

@@ -0,0 +1,18 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

76
vendor/github.com/go-ap/httpsig/handler.go generated vendored Normal file
View File

@@ -0,0 +1,76 @@
// Copyright (C) 2017 Space Monkey, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpsig
import (
"context"
"fmt"
"net/http"
"strings"
)
// ctxKeyIDType is the type used to retrieve the KeyId parametes extracted from the HTTP headers
// and set into the request.Context during call of verifier.Verify
type ctxKeyIDType struct{}
var ctxKeyIDKey = &ctxKeyIDType{}
// WithKeyID retrieves the KeyId parameter from the requests
func WithKeyID(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, ctxKeyIDKey, id)
}
// KeyIDFromContext returns the request ID from the context.
// A zero ID is returned if there are no identifers in the
// current context.
func KeyIDFromContext(ctx context.Context) string {
v := ctx.Value(ctxKeyIDKey)
if v == nil {
return ""
}
return v.(string)
}
// RequireSignature is a http middleware that ensure the incoming request have
// the required signature using verifier v
func RequireSignature(h http.Handler, v *Verifier, realm string) (
out http.Handler) {
var challengeParams []string
if realm != "" {
challengeParams = append(challengeParams,
fmt.Sprintf("realm=%q", realm))
}
if headers := v.RequiredHeaders(); len(headers) > 0 {
challengeParams = append(challengeParams,
fmt.Sprintf("headers=%q", strings.Join(headers, " ")))
}
challenge := "Signature"
if len(challengeParams) > 0 {
challenge += fmt.Sprintf(" %s", strings.Join(challengeParams, ", "))
}
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
keyID, err := v.Verify(req)
if err != nil {
w.Header()["WWW-Authenticate"] = []string{challenge}
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprintln(w, err.Error())
return
}
h.ServeHTTP(w, req.WithContext(WithKeyID(req.Context(), keyID)))
})
}

68
vendor/github.com/go-ap/httpsig/hmac.go generated vendored Normal file
View File

@@ -0,0 +1,68 @@
// Copyright (C) 2017 Space Monkey, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpsig
import (
"crypto"
"crypto/hmac"
"errors"
)
// HMACSHA256 implements keyed HMAC over SHA256 digests
var HMACSHA256 Algorithm = hmacSha256{}
type hmacSha256 struct{}
func (hmacSha256) Name() string {
return "hmac-sha256"
}
func (a hmacSha256) Sign(key interface{}, data []byte) ([]byte, error) {
k := toHMACKey(key)
if k == nil {
return nil, unsupportedAlgorithm(a)
}
return HMACSign(k, crypto.SHA256, data)
}
func (a hmacSha256) Verify(key interface{}, data, sig []byte) error {
k := toHMACKey(key)
if k == nil {
return unsupportedAlgorithm(a)
}
return HMACVerify(k, crypto.SHA256, data, sig)
}
// HMACSign signs a digest of the data hashed using the provided hash and key.
func HMACSign(key []byte, hash crypto.Hash, data []byte) ([]byte, error) {
h := hmac.New(hash.New, key)
if _, err := h.Write(data); err != nil {
return nil, err
}
return h.Sum(nil), nil
}
// HMACVerify verifies a signed digest of the data hashed using the provided
// hash and key.
func HMACVerify(key []byte, hash crypto.Hash, data, sig []byte) error {
actualSig, err := HMACSign(key, hash, data)
if err != nil {
return err
}
if !hmac.Equal(actualSig, sig) {
return errors.New("hmac signature mismatch")
}
return nil
}

44
vendor/github.com/go-ap/httpsig/httpsig.go generated vendored Normal file
View File

@@ -0,0 +1,44 @@
// Copyright (C) 2017 Space Monkey, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpsig
// Algorithm provides methods used to sign/verify signatures.
type Algorithm interface {
Name() string
Sign(key interface{}, data []byte) (sig []byte, err error)
Verify(key interface{}, data, sig []byte) error
}
// KeyGetter is an interface used by the verifier to retrieve a key stored
// by key id.
//
// The following types are supported for the specified algorithms:
// []byte - HMAC signatures
// *rsa.PublicKey - RSA signatures
// *rsa.PrivateKey - RSA signatures
//
// Other types will treated as if no key was returned.
type KeyGetter interface {
GetKey(id string) (interface{}, error)
}
// KeyGetterFunc is a convenience type for implementing a KeyGetter with a
// regular function
type KeyGetterFunc func(id string) (interface{}, error)
// GetKey calls fn(id)
func (fn KeyGetterFunc) GetKey(id string) (interface{}, error) {
return fn(id)
}

44
vendor/github.com/go-ap/httpsig/keystore.go generated vendored Normal file
View File

@@ -0,0 +1,44 @@
// Copyright (C) 2017 Space Monkey, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpsig
import "fmt"
// MemoryKeyStore is a simple in memory key store that implement the
// KeyGetter interface
type MemoryKeyStore struct {
keys map[string]interface{}
}
// NewMemoryKeyStore creates a new MemoryKeyStore
func NewMemoryKeyStore() *MemoryKeyStore {
return &MemoryKeyStore{
keys: make(map[string]interface{}),
}
}
// GetKey implements KeyGetter interface
func (m *MemoryKeyStore) GetKey(id string) (interface{}, error) {
pk, ok := m.keys[id]
if !ok {
return nil, fmt.Errorf("key not found")
}
return pk, nil
}
// SetKey link id to a key
func (m *MemoryKeyStore) SetKey(id string, key interface{}) {
m.keys[id] = key
}

92
vendor/github.com/go-ap/httpsig/rsa.go generated vendored Normal file
View File

@@ -0,0 +1,92 @@
// Copyright (C) 2017 Space Monkey, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpsig
import (
"crypto"
"crypto/rsa"
)
// RSASHA1 implements RSA PKCS1v15 signatures over a SHA1 digest
var RSASHA1 Algorithm = rsaSha1{}
type rsaSha1 struct{}
func (rsaSha1) Name() string {
return "rsa-sha1"
}
func (a rsaSha1) Sign(key interface{}, data []byte) ([]byte, error) {
k := toRSAPrivateKey(key)
if k == nil {
return nil, unsupportedAlgorithm(a)
}
return RSASign(k, crypto.SHA1, data)
}
func (a rsaSha1) Verify(key interface{}, data, sig []byte) error {
k := toRSAPublicKey(key)
if k == nil {
return unsupportedAlgorithm(a)
}
return RSAVerify(k, crypto.SHA1, data, sig)
}
// RSASHA256 implements RSA PKCS1v15 signatures over a SHA256 digest
var RSASHA256 Algorithm = rsaSha256{}
type rsaSha256 struct{}
func (rsaSha256) Name() string {
return "rsa-sha256"
}
func (a rsaSha256) Sign(key interface{}, data []byte) ([]byte, error) {
k := toRSAPrivateKey(key)
if k == nil {
return nil, unsupportedAlgorithm(a)
}
return RSASign(k, crypto.SHA256, data)
}
func (a rsaSha256) Verify(key interface{}, data, sig []byte) error {
k := toRSAPublicKey(key)
if k == nil {
return unsupportedAlgorithm(a)
}
return RSAVerify(k, crypto.SHA256, data, sig)
}
// RSASign signs a digest of the data hashed using the provided hash
func RSASign(key *rsa.PrivateKey, hash crypto.Hash, data []byte) (
signature []byte, err error) {
h := hash.New()
if _, err := h.Write(data); err != nil {
return nil, err
}
return rsa.SignPKCS1v15(Rand, key, hash, h.Sum(nil))
}
// RSAVerify verifies a signed digest of the data hashed using the provided hash
func RSAVerify(key *rsa.PublicKey, hash crypto.Hash, data, sig []byte) (
err error) {
h := hash.New()
if _, err := h.Write(data); err != nil {
return err
}
return rsa.VerifyPKCS1v15(key, hash, h.Sum(nil), sig)
}

122
vendor/github.com/go-ap/httpsig/sign.go generated vendored Normal file
View File

@@ -0,0 +1,122 @@
// Copyright (C) 2017 Space Monkey, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpsig
import (
"crypto"
"crypto/rsa"
"encoding/base64"
"fmt"
"net/http"
"strings"
"time"
)
// Signer is the type used by HTTP clients to sign their request
type Signer struct {
id string
key interface{}
algo Algorithm
headers []string
}
// NewSigner contructs a signer with the specified key id, key, algorithm,
// and headers to sign. By default, if headers is nil or empty, the
// request-target and date headers will be signed.
func NewSigner(id string, key interface{}, algo Algorithm, headers []string) (
signer *Signer) {
s := &Signer{
id: id,
key: key,
algo: algo,
}
// copy the headers slice, lowercasing as necessary
if len(headers) == 0 {
headers = []string{"(request-target)", "date"}
}
s.headers = make([]string, 0, len(headers))
for _, header := range headers {
s.headers = append(s.headers, strings.ToLower(header))
}
return s
}
// NewRSASHA1Signer contructs a signer with the specified key id, rsa private
// key and headers to sign.
func NewRSASHA1Signer(id string, key *rsa.PrivateKey, headers []string) (signer *Signer) {
return NewSigner(id, key, RSASHA1, headers)
}
// NewRSASHA256Signer contructs a signer with the specified key id, rsa private
// key and headers to sign.
func NewRSASHA256Signer(id string, key *rsa.PrivateKey, headers []string) (signer *Signer) {
return NewSigner(id, key, RSASHA256, headers)
}
// NewHMACSHA256Signer contructs a signer with the specified key id, hmac key,
// and headers to sign.
func NewHMACSHA256Signer(id string, key []byte, headers []string) (
signer *Signer) {
return NewSigner(id, key, HMACSHA256, headers)
}
// NewEd25519Signer contructs a signer with the specified key id, Ed25519 key,
// and headers to sign.
func NewEd25519Signer(id string, key crypto.PrivateKey, headers []string) *Signer {
return NewSigner(id, key, Ed25519, headers)
}
// Sign signs an http request and adds the signature to the authorization header
func (r *Signer) Sign(req *http.Request) error {
now := time.Now()
params, err := signRequest(r.id, r.key, r.algo, r.headers, now, now.Add(time.Minute), req)
if err != nil {
return err
}
req.Header.Set("Authorization", "Signature "+params)
return nil
}
// signRequest signs an http request and returns the parameter string.
func signRequest(id string, key interface{}, algo Algorithm, headers []string, created, expires time.Time, req *http.Request) (params string, err error) {
signatureData, err := BuildSignatureData(req, headers, created, expires)
if err != nil {
return "", err
}
signature, err := algo.Sign(key, signatureData)
if err != nil {
return "", err
}
// The headers parameter can be omitted if the only header is "Date". The
// receiving end assumes ["date"] if no headers parameter is present.
var headersParam string
if !(len(headers) == 1 && headers[0] == "date") {
headersParam = fmt.Sprintf("headers=%q,", strings.Join(headers, " "))
}
return fmt.Sprintf(
"keyId=%q,algorithm=%q,created=\"%d\",expires=\"%d\",%ssignature=%q",
id,
algo.Name(),
created.Unix(),
expires.Unix(),
headersParam,
base64.StdEncoding.EncodeToString(signature)), nil
}

249
vendor/github.com/go-ap/httpsig/verify.go generated vendored Normal file
View File

@@ -0,0 +1,249 @@
// Copyright (C) 2017 Space Monkey, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpsig
import (
"crypto"
"encoding/base64"
"fmt"
"net/http"
"regexp"
"strconv"
"strings"
"time"
)
// Verifier is used by by the HTTP server to verify the incoming HTTP requests
type Verifier struct {
keyGetter KeyGetter
requiredHeaders []string
}
// NewVerifier creates a new Verifier using kg to get the key
// mapped to the ID received in the requests
func NewVerifier(kg KeyGetter) *Verifier {
v := &Verifier{
keyGetter: kg,
}
v.SetRequiredHeaders(nil)
return v
}
// RequiredHeaders returns the required header the client have to include in
// the signature
func (v *Verifier) RequiredHeaders() []string {
return append([]string{}, v.requiredHeaders...)
}
// SetRequiredHeaders set the list of headers to be included by the client to generate the signature
func (v *Verifier) SetRequiredHeaders(headers []string) {
if len(headers) == 0 {
headers = []string{"date"}
}
requiredHeaders := make([]string, 0, len(headers))
for _, h := range headers {
requiredHeaders = append(requiredHeaders, strings.ToLower(h))
}
v.requiredHeaders = requiredHeaders
}
// Verify parses req and verify the signature using the key returned by
// the keyGetter. It returns the KeyId parameter from he signature header
// and a nil error if the signature verifies, an error otherwise
func (v *Verifier) Verify(req *http.Request) (string, error) {
// retrieve and validate params from the request
params := getParamsFromAuthHeader(req)
if params == nil {
return "", fmt.Errorf("no params present")
}
if params.KeyID == "" {
return "", fmt.Errorf("keyId is required")
}
if params.Algorithm == "" {
return "", fmt.Errorf("algorithm is required")
}
if len(params.Signature) == 0 {
return "", fmt.Errorf("signature is required")
}
if len(params.Headers) == 0 {
params.Headers = []string{"date"}
}
header_check:
for _, h := range v.requiredHeaders {
for _, header := range params.Headers {
if strings.EqualFold(h, header) {
continue header_check
}
}
return "", fmt.Errorf("missing required header in signature %q",
h)
}
// calculate signature string for request
sigData, err := BuildSignatureData(req, params.Headers, params.Created, params.Expires)
if err != nil {
return "", err
}
// look up key based on keyId
key, err := v.keyGetter.GetKey(params.KeyID)
if err != nil {
return "", err
}
// we still leave this sanity check
if key == nil {
return "", fmt.Errorf("no key with id %q", params.KeyID)
}
switch params.Algorithm {
case "rsa-sha1":
rsaPubkey := toRSAPublicKey(key)
if rsaPubkey == nil {
return "", fmt.Errorf("algorithm %q is not supported by key %q",
params.Algorithm, params.KeyID)
}
return params.KeyID, RSAVerify(rsaPubkey, crypto.SHA1, sigData, params.Signature)
case "rsa-sha256":
rsaPubkey := toRSAPublicKey(key)
if rsaPubkey == nil {
return "", fmt.Errorf("algorithm %q is not supported by key %q",
params.Algorithm, params.KeyID)
}
return params.KeyID, RSAVerify(rsaPubkey, crypto.SHA256, sigData, params.Signature)
case "hmac-sha256":
hmacKey := toHMACKey(key)
if hmacKey == nil {
return "", fmt.Errorf("algorithm %q is not supported by key %q",
params.Algorithm, params.KeyID)
}
return params.KeyID, HMACVerify(hmacKey, crypto.SHA256, sigData, params.Signature)
case "ed25519":
ed25519Key := toEd25519PublicKey(key)
if ed25519Key == nil {
return "", fmt.Errorf("algorithm %q is not supported by key %q",
params.Algorithm, params.KeyID)
}
return params.KeyID, Ed25519Verify(ed25519Key, sigData, params.Signature)
default:
return "", fmt.Errorf("unsupported algorithm %q", params.Algorithm)
}
}
// paramRE scans out recognized parameter keypairs. accepted values are those
// that are quoted
var paramRE = regexp.MustCompile(`(?U)\s*([a-zA-Z][a-zA-Z0-9_]*)\s*=\s*"(.*)"\s*`)
// Params holds the field requires to build the signature string
type Params struct {
KeyID string
Algorithm string
Headers []string
Signature []byte
Created time.Time
Expires time.Time
}
func getParamsFromAuthHeader(req *http.Request) *Params {
return getParams(req, "Authorization", "Signature ")
}
func getParams(req *http.Request, header, prefix string) *Params {
values := req.Header[http.CanonicalHeaderKey(header)]
// last well-formed parameter wins
for i := len(values) - 1; i >= 0; i-- {
value := values[i]
if prefix != "" {
if trimmed := strings.TrimPrefix(value, prefix); trimmed != value {
value = trimmed
} else {
continue
}
}
matches := paramRE.FindAllStringSubmatch(value, -1)
if matches == nil {
continue
}
params := Params{}
// malformed parameters get ignored.
for _, match := range matches {
switch match[1] {
case "keyId":
params.KeyID = match[2]
case "algorithm":
if algorithm, ok := parseAlgorithm(match[2]); ok {
params.Algorithm = algorithm
}
case "headers":
if headers, ok := parseHeaders(match[2]); ok {
params.Headers = headers
}
case "signature":
if signature, ok := parseSignature(match[2]); ok {
params.Signature = signature
}
case "created":
if created, ok := parseTime(match[2]); ok {
params.Created = created
}
case "expires":
if expires, ok := parseTime(match[2]); ok {
params.Expires = expires
}
}
}
return &params
}
return nil
}
// parseAlgorithm parses recognized algorithm values
func parseAlgorithm(s string) (algorithm string, ok bool) {
s = strings.TrimSpace(s)
switch s {
case "rsa-sha1", "rsa-sha256", "hmac-sha256", "ed25519":
return s, true
}
return "", false
}
// parseHeaders parses a space separated list of header values.
func parseHeaders(s string) (headers []string, ok bool) {
for _, header := range strings.Split(s, " ") {
if header != "" {
headers = append(headers, strings.ToLower(header))
}
}
return headers, true
}
func parseSignature(s string) (signature []byte, ok bool) {
signature, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return nil, false
}
return signature, true
}
func parseTime(s string) (t time.Time, ok bool) {
sec, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return t, false
}
return time.Unix(sec, 0), true
}