github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/protocol/v2/secure_request.go (about)

     1  // Copyright (c) 2022, R.I. Pienaar and the Choria Project contributors
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package v2
     6  
     7  import (
     8  	"context"
     9  	"encoding/json"
    10  	"fmt"
    11  	"strings"
    12  	"sync"
    13  
    14  	"github.com/choria-io/go-choria/inter"
    15  	"github.com/choria-io/go-choria/protocol"
    16  	log "github.com/sirupsen/logrus"
    17  )
    18  
    19  // SecureRequest contains 1 serialized Request signed and with the related JWTs attached
    20  type SecureRequest struct {
    21  	// The protocol version for this secure request `io.choria.protocol.v2.secure_request` / protocol.SecureRequestV2
    22  	Protocol protocol.ProtocolVersion `json:"protocol"`
    23  	// The request held in the Secure Request
    24  	MessageBody []byte `json:"request"`
    25  	// A signature made using the ed25519 seed of the caller or signer
    26  	Signature []byte `json:"signature"`
    27  	// The JWT of the caller
    28  	CallerJWT string `json:"caller"`
    29  	// The JWT of the delegated signer, present when the AAA server is used
    30  	SignerJWT string `json:"signer,omitempty"`
    31  
    32  	security inter.SecurityProvider
    33  	mu       sync.Mutex
    34  }
    35  
    36  // NewSecureRequest creates a choria:secure:request:1
    37  func NewSecureRequest(request protocol.Request, security inter.SecurityProvider) (protocol.SecureRequest, error) {
    38  	if security.BackingTechnology() != inter.SecurityTechnologyED25519JWT {
    39  		return nil, ErrIncorrectProtocol
    40  	}
    41  
    42  	secure := &SecureRequest{
    43  		Protocol: protocol.SecureRequestV2,
    44  		security: security,
    45  	}
    46  
    47  	// TODO: we might choose to only support secure mode, but complicated with provisioning
    48  	if protocol.IsSecure() {
    49  		token, err := security.TokenBytes()
    50  		if err != nil {
    51  			return nil, err
    52  		}
    53  
    54  		secure.CallerJWT = string(token)
    55  	}
    56  
    57  	err := secure.SetMessage(request)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	return secure, nil
    63  }
    64  
    65  // NewRemoteSignedSecureRequest is a NewSecureRequest that delegates the signing to a remote signer like aaasvc
    66  func NewRemoteSignedSecureRequest(ctx context.Context, request protocol.Request, security inter.SecurityProvider) (protocol.SecureRequest, error) {
    67  	if security.BackingTechnology() != inter.SecurityTechnologyED25519JWT {
    68  		return nil, ErrIncorrectProtocol
    69  	}
    70  
    71  	// no need for remote stuff, we don't do any signing or certs,
    72  	// additionally the service hosting the remote signing service isnt
    73  	// secured by choria protocol since at calling time the client does
    74  	// not have a cert etc, but the request expects a signed JWT so that
    75  	// provides the security of that request
    76  	//
    77  	// TODO: we might choose to only support secure mode, but complicated with provisioning, provisioning though shouldnt make requests
    78  	if !protocol.IsSecure() || protocol.IsRemoteSignerAgent(request.Agent()) {
    79  		return NewSecureRequest(request, security)
    80  	}
    81  
    82  	reqj, err := request.JSON()
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	secj, err := security.RemoteSignRequest(ctx, reqj)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	secure := &SecureRequest{
    93  		Protocol: protocol.SecureRequestV2,
    94  		security: security,
    95  	}
    96  
    97  	err = json.Unmarshal(secj, &secure)
    98  	if err != nil {
    99  		return nil, fmt.Errorf("could not parse signed request: %s", err)
   100  	}
   101  
   102  	if secure.SignerJWT == "" {
   103  		return nil, fmt.Errorf("remote signer did not set a signer JWT")
   104  	}
   105  
   106  	// We set our caller token here despite having done a delegated request because the
   107  	// delegation would not know our token, so we just set this here which will not modify
   108  	// the secure payload or its signature etc
   109  	//
   110  	// TODO: we might choose to only support secure mode, but complicated with provisioning
   111  	if protocol.IsSecure() {
   112  		token, err := security.TokenBytes()
   113  		if err != nil {
   114  			return nil, err
   115  		}
   116  
   117  		secure.CallerJWT = string(token)
   118  	}
   119  
   120  	return secure, nil
   121  }
   122  
   123  // NewSecureRequestFromTransport creates a new choria:secure:request:1 from the data contained in a Transport message
   124  func NewSecureRequestFromTransport(message protocol.TransportMessage, security inter.SecurityProvider, skipvalidate bool) (protocol.SecureRequest, error) {
   125  	if security.BackingTechnology() != inter.SecurityTechnologyED25519JWT {
   126  		return nil, ErrIncorrectProtocol
   127  	}
   128  
   129  	secure := &SecureRequest{
   130  		Protocol: protocol.SecureRequestV2,
   131  		security: security,
   132  	}
   133  
   134  	data, err := message.Message()
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	err = secure.IsValidJSON(data)
   140  	if err != nil {
   141  		return nil, fmt.Errorf("the JSON body from the TransportMessage is not a valid SecureRequest: %w", err)
   142  	}
   143  
   144  	err = json.Unmarshal(data, &secure)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	if !skipvalidate {
   150  		if !secure.Valid() {
   151  			return nil, fmt.Errorf("secure request messages created from Transport Message did not pass security validation")
   152  		}
   153  	}
   154  
   155  	return secure, nil
   156  }
   157  
   158  func (r *SecureRequest) SetMessage(request protocol.Request) error {
   159  	r.mu.Lock()
   160  	defer r.mu.Unlock()
   161  
   162  	j, err := request.JSON()
   163  	if err != nil {
   164  		protocolErrorCtr.Inc()
   165  		return fmt.Errorf("could not JSON encode reply message to store it in the Secure Request: %w", err)
   166  	}
   167  
   168  	r.Signature = []byte("insecure")
   169  
   170  	// TODO: we might choose to only support secure mode, but complicated with provisioning
   171  	if protocol.IsSecure() && !protocol.IsRemoteSignerAgent(request.Agent()) {
   172  		sig, err := r.security.SignBytes(j)
   173  		if err != nil {
   174  			return err
   175  		}
   176  		r.Signature = sig
   177  	}
   178  
   179  	r.MessageBody = j
   180  
   181  	return nil
   182  }
   183  
   184  func (r *SecureRequest) Valid() bool {
   185  	// TODO: we might choose to only support secure mode, but complicated with provisioning
   186  	if !protocol.IsSecure() {
   187  		log.Debug("Bypassing validation on secure request due to build time flags")
   188  		return true
   189  	}
   190  
   191  	jwts := [][]byte{[]byte(r.CallerJWT)}
   192  	// delegated signatures
   193  	if r.SignerJWT != "" {
   194  		jwts = append(jwts, []byte(r.SignerJWT))
   195  	}
   196  
   197  	should, _ := r.security.VerifySignatureBytes(r.MessageBody, r.Signature, jwts...)
   198  	if !should {
   199  		log.Errorf("Signature in request did not pass validation")
   200  		invalidCtr.Inc()
   201  		return false
   202  	}
   203  
   204  	req, err := NewRequestFromSecureRequest(r)
   205  	if err != nil {
   206  		log.Errorf("Could not create Request to validate Secure Request with: %s", err)
   207  		protocolErrorCtr.Inc()
   208  		return false
   209  	}
   210  
   211  	_, err = r.security.ShouldAllowCaller(req.CallerID(), jwts...)
   212  	if err != nil {
   213  		log.Errorf("Caller verification failed: %s", err)
   214  		protocolErrorCtr.Inc()
   215  		return false
   216  	}
   217  
   218  	validCtr.Inc()
   219  
   220  	return true
   221  }
   222  
   223  func (r *SecureRequest) JSON() ([]byte, error) {
   224  	r.mu.Lock()
   225  	j, err := json.Marshal(r)
   226  	r.mu.Unlock()
   227  	if err != nil {
   228  		protocolErrorCtr.Inc()
   229  		return nil, fmt.Errorf("could not JSON Marshal: %s", err)
   230  	}
   231  
   232  	if err = r.IsValidJSON(j); err != nil {
   233  		return nil, fmt.Errorf("%w: %s", ErrInvalidJSON, err)
   234  	}
   235  
   236  	return j, nil
   237  }
   238  
   239  func (r *SecureRequest) Version() protocol.ProtocolVersion {
   240  	r.mu.Lock()
   241  	defer r.mu.Unlock()
   242  
   243  	return r.Protocol
   244  }
   245  
   246  func (r *SecureRequest) IsValidJSON(data []byte) error {
   247  	_, errors, err := schemaValidate(protocol.SecureRequestV2, data)
   248  	if err != nil {
   249  		protocolErrorCtr.Inc()
   250  		return fmt.Errorf("could not validate SecureRequest JSON data: %s", err)
   251  	}
   252  
   253  	if len(errors) != 0 {
   254  		return fmt.Errorf("%w: %s", ErrInvalidJSON, strings.Join(errors, ", "))
   255  	}
   256  
   257  	return nil
   258  }
   259  
   260  func (r *SecureRequest) Message() []byte {
   261  	r.mu.Lock()
   262  	defer r.mu.Unlock()
   263  
   264  	return r.MessageBody
   265  }
   266  
   267  func (r *SecureRequest) SetSigner(signer []byte) error {
   268  	r.mu.Lock()
   269  	defer r.mu.Unlock()
   270  
   271  	r.SignerJWT = string(signer)
   272  
   273  	return nil
   274  }
   275  
   276  func (r *SecureRequest) CallerPublicData() string {
   277  	r.mu.Lock()
   278  	defer r.mu.Unlock()
   279  
   280  	return r.CallerJWT
   281  }