mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2026-04-15 01:41:56 +00:00
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:
191
vendor/github.com/go-ap/httpsig/LICENSE
generated
vendored
Normal file
191
vendor/github.com/go-ap/httpsig/LICENSE
generated
vendored
Normal 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
54
vendor/github.com/go-ap/httpsig/Makefile
generated
vendored
Normal 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
138
vendor/github.com/go-ap/httpsig/README.md
generated
vendored
Normal 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
140
vendor/github.com/go-ap/httpsig/common.go
generated
vendored
Normal 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
58
vendor/github.com/go-ap/httpsig/ed25519.go
generated
vendored
Normal 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
9
vendor/github.com/go-ap/httpsig/go.mod
generated
vendored
Normal 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
18
vendor/github.com/go-ap/httpsig/go.sum
generated
vendored
Normal 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
76
vendor/github.com/go-ap/httpsig/handler.go
generated
vendored
Normal 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
68
vendor/github.com/go-ap/httpsig/hmac.go
generated
vendored
Normal 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
44
vendor/github.com/go-ap/httpsig/httpsig.go
generated
vendored
Normal 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
44
vendor/github.com/go-ap/httpsig/keystore.go
generated
vendored
Normal 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
92
vendor/github.com/go-ap/httpsig/rsa.go
generated
vendored
Normal 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
122
vendor/github.com/go-ap/httpsig/sign.go
generated
vendored
Normal 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
249
vendor/github.com/go-ap/httpsig/verify.go
generated
vendored
Normal 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 ¶ms
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user