| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- // Copyright (c) 2016, 2018, 2020, Oracle and/or its affiliates. All rights reserved.
- // This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
- package common
- import (
- "bytes"
- "crypto"
- "crypto/rand"
- "crypto/rsa"
- "crypto/sha256"
- "encoding/base64"
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "strings"
- )
- // HTTPRequestSigner the interface to sign a request
- type HTTPRequestSigner interface {
- Sign(r *http.Request) error
- }
- // KeyProvider interface that wraps information about the key's account owner
- type KeyProvider interface {
- PrivateRSAKey() (*rsa.PrivateKey, error)
- KeyID() (string, error)
- }
- const signerVersion = "1"
- // SignerBodyHashPredicate a function that allows to disable/enable body hashing
- // of requests and headers associated with body content
- type SignerBodyHashPredicate func(r *http.Request) bool
- // ociRequestSigner implements the http-signatures-draft spec
- // as described in https://tools.ietf.org/html/draft-cavage-http-signatures-08
- type ociRequestSigner struct {
- KeyProvider KeyProvider
- GenericHeaders []string
- BodyHeaders []string
- ShouldHashBody SignerBodyHashPredicate
- }
- var (
- defaultGenericHeaders = []string{"date", "(request-target)", "host"}
- defaultBodyHeaders = []string{"content-length", "content-type", "x-content-sha256"}
- defaultBodyHashPredicate = func(r *http.Request) bool {
- return r.Method == http.MethodPost || r.Method == http.MethodPut || r.Method == http.MethodPatch
- }
- )
- // DefaultGenericHeaders list of default generic headers that is used in signing
- func DefaultGenericHeaders() []string {
- return makeACopy(defaultGenericHeaders)
- }
- // DefaultBodyHeaders list of default body headers that is used in signing
- func DefaultBodyHeaders() []string {
- return makeACopy(defaultBodyHeaders)
- }
- // DefaultRequestSigner creates a signer with default parameters.
- func DefaultRequestSigner(provider KeyProvider) HTTPRequestSigner {
- return RequestSigner(provider, defaultGenericHeaders, defaultBodyHeaders)
- }
- // RequestSignerExcludeBody creates a signer without hash the body.
- func RequestSignerExcludeBody(provider KeyProvider) HTTPRequestSigner {
- bodyHashPredicate := func(r *http.Request) bool {
- // week request signer will not hash the body
- return false
- }
- return RequestSignerWithBodyHashingPredicate(provider, defaultGenericHeaders, defaultBodyHeaders, bodyHashPredicate)
- }
- // NewSignerFromOCIRequestSigner creates a copy of the request signer and attaches the new SignerBodyHashPredicate
- // returns an error if the passed signer is not of type ociRequestSigner
- func NewSignerFromOCIRequestSigner(oldSigner HTTPRequestSigner, predicate SignerBodyHashPredicate) (HTTPRequestSigner, error) {
- if oldS, ok := oldSigner.(ociRequestSigner); ok {
- s := ociRequestSigner{
- KeyProvider: oldS.KeyProvider,
- GenericHeaders: oldS.GenericHeaders,
- BodyHeaders: oldS.BodyHeaders,
- ShouldHashBody: predicate,
- }
- return s, nil
- }
- return nil, fmt.Errorf("can not create a signer, input signer needs to be of type ociRequestSigner")
- }
- // RequestSigner creates a signer that utilizes the specified headers for signing
- // and the default predicate for using the body of the request as part of the signature
- func RequestSigner(provider KeyProvider, genericHeaders, bodyHeaders []string) HTTPRequestSigner {
- return ociRequestSigner{
- KeyProvider: provider,
- GenericHeaders: genericHeaders,
- BodyHeaders: bodyHeaders,
- ShouldHashBody: defaultBodyHashPredicate}
- }
- // RequestSignerWithBodyHashingPredicate creates a signer that utilizes the specified headers for signing, as well as a predicate for using
- // the body of the request and bodyHeaders parameter as part of the signature
- func RequestSignerWithBodyHashingPredicate(provider KeyProvider, genericHeaders, bodyHeaders []string, shouldHashBody SignerBodyHashPredicate) HTTPRequestSigner {
- return ociRequestSigner{
- KeyProvider: provider,
- GenericHeaders: genericHeaders,
- BodyHeaders: bodyHeaders,
- ShouldHashBody: shouldHashBody}
- }
- func (signer ociRequestSigner) getSigningHeaders(r *http.Request) []string {
- var result []string
- result = append(result, signer.GenericHeaders...)
- if signer.ShouldHashBody(r) {
- result = append(result, signer.BodyHeaders...)
- }
- return result
- }
- func (signer ociRequestSigner) getSigningString(request *http.Request) string {
- signingHeaders := signer.getSigningHeaders(request)
- signingParts := make([]string, len(signingHeaders))
- for i, part := range signingHeaders {
- var value string
- part = strings.ToLower(part)
- switch part {
- case "(request-target)":
- value = getRequestTarget(request)
- case "host":
- value = request.URL.Host
- if len(value) == 0 {
- value = request.Host
- }
- default:
- value = request.Header.Get(part)
- }
- signingParts[i] = fmt.Sprintf("%s: %s", part, value)
- }
- signingString := strings.Join(signingParts, "\n")
- return signingString
- }
- func getRequestTarget(request *http.Request) string {
- lowercaseMethod := strings.ToLower(request.Method)
- return fmt.Sprintf("%s %s", lowercaseMethod, request.URL.RequestURI())
- }
- func calculateHashOfBody(request *http.Request) (err error) {
- var hash string
- hash, err = GetBodyHash(request)
- if err != nil {
- return
- }
- request.Header.Set(requestHeaderXContentSHA256, hash)
- return
- }
- // drainBody reads all of b to memory and then returns two equivalent
- // ReadClosers yielding the same bytes.
- //
- // It returns an error if the initial slurp of all bytes fails. It does not attempt
- // to make the returned ReadClosers have identical error-matching behavior.
- func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
- if b == http.NoBody {
- // No copying needed. Preserve the magic sentinel meaning of NoBody.
- return http.NoBody, http.NoBody, nil
- }
- var buf bytes.Buffer
- if _, err = buf.ReadFrom(b); err != nil {
- return nil, b, err
- }
- if err = b.Close(); err != nil {
- return nil, b, err
- }
- return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil
- }
- func hashAndEncode(data []byte) string {
- hashedContent := sha256.Sum256(data)
- hash := base64.StdEncoding.EncodeToString(hashedContent[:])
- return hash
- }
- // GetBodyHash creates a base64 string from the hash of body the request
- func GetBodyHash(request *http.Request) (hashString string, err error) {
- if request.Body == nil {
- request.ContentLength = 0
- request.Header.Set(requestHeaderContentLength, fmt.Sprintf("%v", request.ContentLength))
- return hashAndEncode([]byte("")), nil
- }
- var data []byte
- bReader := request.Body
- bReader, request.Body, err = drainBody(request.Body)
- if err != nil {
- return "", fmt.Errorf("can not read body of request while calculating body hash: %s", err.Error())
- }
- data, err = ioutil.ReadAll(bReader)
- if err != nil {
- return "", fmt.Errorf("can not read body of request while calculating body hash: %s", err.Error())
- }
- // Since the request can be coming from a binary body. Make an attempt to set the body length
- request.ContentLength = int64(len(data))
- request.Header.Set(requestHeaderContentLength, fmt.Sprintf("%v", request.ContentLength))
- hashString = hashAndEncode(data)
- return
- }
- func (signer ociRequestSigner) computeSignature(request *http.Request) (signature string, err error) {
- signingString := signer.getSigningString(request)
- hasher := sha256.New()
- hasher.Write([]byte(signingString))
- hashed := hasher.Sum(nil)
- privateKey, err := signer.KeyProvider.PrivateRSAKey()
- if err != nil {
- return
- }
- var unencodedSig []byte
- unencodedSig, e := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed)
- if e != nil {
- err = fmt.Errorf("can not compute signature while signing the request %s: ", e.Error())
- return
- }
- signature = base64.StdEncoding.EncodeToString(unencodedSig)
- return
- }
- // Sign signs the http request, by inspecting the necessary headers. Once signed
- // the request will have the proper 'Authorization' header set, otherwise
- // and error is returned
- func (signer ociRequestSigner) Sign(request *http.Request) (err error) {
- if signer.ShouldHashBody(request) {
- err = calculateHashOfBody(request)
- if err != nil {
- return
- }
- }
- var signature string
- if signature, err = signer.computeSignature(request); err != nil {
- return
- }
- signingHeaders := strings.Join(signer.getSigningHeaders(request), " ")
- var keyID string
- if keyID, err = signer.KeyProvider.KeyID(); err != nil {
- return
- }
- authValue := fmt.Sprintf("Signature version=\"%s\",headers=\"%s\",keyId=\"%s\",algorithm=\"rsa-sha256\",signature=\"%s\"",
- signerVersion, signingHeaders, keyID, signature)
- request.Header.Set(requestHeaderAuthorization, authValue)
- return
- }
|