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

     1  // Copyright (c) 2017-2022, R.I. Pienaar and the Choria Project contributors
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package v1
     6  
     7  import (
     8  	"encoding/base64"
     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 public cert attached
    20  type SecureRequest struct {
    21  	Protocol          protocol.ProtocolVersion `json:"protocol"`
    22  	MessageBody       string                   `json:"message"`
    23  	Signature         string                   `json:"signature"`
    24  	PublicCertificate string                   `json:"pubcert"`
    25  
    26  	security inter.SecurityProvider
    27  	mu       sync.Mutex
    28  }
    29  
    30  // SetMessage sets the message contained in the Request and updates the signature
    31  func (r *SecureRequest) SetMessage(request protocol.Request) (err error) {
    32  	r.mu.Lock()
    33  	defer r.mu.Unlock()
    34  
    35  	j, err := request.JSON()
    36  	if err != nil {
    37  		protocolErrorCtr.Inc()
    38  		return fmt.Errorf("could not JSON encode reply message to store it in the Secure Request: %s", err)
    39  	}
    40  
    41  	r.Signature = "insecure"
    42  
    43  	if protocol.IsSecure() && !protocol.IsRemoteSignerAgent(request.Agent()) {
    44  		var signature []byte
    45  
    46  		signature, err = r.security.SignBytes(j)
    47  		if err != nil {
    48  			// registration when doing anon tls might not have a certificate - so we allow that to go unsigned
    49  			if !protocol.IsRegistrationAgent(request.Agent()) {
    50  				return fmt.Errorf("could not sign message string: %s", err)
    51  			}
    52  			signature = []byte("insecure registration")
    53  		}
    54  
    55  		r.Signature = base64.StdEncoding.EncodeToString(signature)
    56  	}
    57  
    58  	r.MessageBody = string(j)
    59  
    60  	return nil
    61  }
    62  
    63  // Message retrieves the stored message.  It will be a JSON encoded version of the request set via SetMessage
    64  func (r *SecureRequest) Message() []byte {
    65  	r.mu.Lock()
    66  	defer r.mu.Unlock()
    67  
    68  	return []byte(r.MessageBody)
    69  }
    70  
    71  // Valid determines if the request is valid
    72  func (r *SecureRequest) Valid() bool {
    73  	// should not be locked
    74  
    75  	if !protocol.IsSecure() {
    76  		log.Debug("Bypassing validation on secure request due to build time flags")
    77  		return true
    78  	}
    79  
    80  	req, err := NewRequestFromSecureRequest(r)
    81  	if err != nil {
    82  		log.Errorf("Could not create Request to validate Secure Request with: %s", err)
    83  		protocolErrorCtr.Inc()
    84  		return false
    85  	}
    86  
    87  	certname, err := r.security.CallerIdentity(req.CallerID())
    88  	if err != nil {
    89  		log.Errorf("Could not extract certname from caller: %s", err)
    90  		protocolErrorCtr.Inc()
    91  		return false
    92  	}
    93  
    94  	_, err = r.security.ShouldAllowCaller(certname, []byte(r.PublicCertificate))
    95  	if err != nil {
    96  		log.Errorf("Client Certificate verification failed: %s", err)
    97  		protocolErrorCtr.Inc()
    98  		return false
    99  	}
   100  
   101  	sig, err := base64.StdEncoding.DecodeString(r.Signature)
   102  	if err != nil {
   103  		log.Errorf("Could not bas64 decode signature: %s", err)
   104  		protocolErrorCtr.Inc()
   105  		return false
   106  	}
   107  
   108  	should, _ := r.security.VerifySignatureBytes([]byte(r.MessageBody), sig, []byte(r.PublicCertificate))
   109  	if !should {
   110  		log.Errorf("Signature in request did not pass validation with embedded public certificate")
   111  		invalidCtr.Inc()
   112  		return false
   113  	}
   114  
   115  	validCtr.Inc()
   116  
   117  	return true
   118  }
   119  
   120  // JSON creates a JSON encoded request
   121  func (r *SecureRequest) JSON() ([]byte, error) {
   122  	r.mu.Lock()
   123  	j, err := json.Marshal(r)
   124  	r.mu.Unlock()
   125  	if err != nil {
   126  		protocolErrorCtr.Inc()
   127  		return nil, fmt.Errorf("could not JSON Marshal: %s", err)
   128  	}
   129  
   130  	if err = r.IsValidJSON(j); err != nil {
   131  		return nil, fmt.Errorf("the JSON produced from the SecureRequest does not pass validation: %s", err)
   132  	}
   133  
   134  	return j, nil
   135  }
   136  
   137  // Version retrieves the protocol version for this message
   138  func (r *SecureRequest) Version() protocol.ProtocolVersion {
   139  	r.mu.Lock()
   140  	defer r.mu.Unlock()
   141  
   142  	return r.Protocol
   143  }
   144  
   145  // IsValidJSON validates the given JSON data against the schema
   146  func (r *SecureRequest) IsValidJSON(data []byte) (err error) {
   147  	_, errors, err := schemaValidate(protocol.SecureRequestV1, data)
   148  	if err != nil {
   149  		protocolErrorCtr.Inc()
   150  		return fmt.Errorf("could not validate SecureRequest JSON data: %s", err)
   151  	}
   152  
   153  	if len(errors) != 0 {
   154  		return fmt.Errorf("supplied JSON document is not a valid SecureRequest message: %s", strings.Join(errors, ", "))
   155  	}
   156  
   157  	return nil
   158  }
   159  
   160  func (r *SecureRequest) SetSigner(_ []byte) error {
   161  	return nil
   162  }
   163  
   164  func (r *SecureRequest) CallerPublicData() string {
   165  	r.mu.Lock()
   166  	defer r.mu.Unlock()
   167  
   168  	return r.PublicCertificate
   169  }