github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/protocol/v1/security_reply.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  )
    17  
    18  // SecureReply contains 1 serialized Reply hashed
    19  type SecureReply struct {
    20  	Protocol    protocol.ProtocolVersion `json:"protocol"`
    21  	MessageBody string                   `json:"message"`
    22  	Hash        string                   `json:"hash"`
    23  
    24  	security inter.SecurityProvider
    25  	mu       sync.Mutex
    26  }
    27  
    28  // SetMessage sets the message contained in the Reply and updates the hash
    29  func (r *SecureReply) SetMessage(reply protocol.Reply) (err error) {
    30  	r.mu.Lock()
    31  	defer r.mu.Unlock()
    32  
    33  	j, err := reply.JSON()
    34  	if err != nil {
    35  		protocolErrorCtr.Inc()
    36  		return fmt.Errorf("could not JSON encode reply message to store it in the Secure Reply: %s", err)
    37  	}
    38  
    39  	hash := r.security.ChecksumBytes(j)
    40  	r.MessageBody = string(j)
    41  	r.Hash = base64.StdEncoding.EncodeToString(hash[:])
    42  
    43  	return nil
    44  }
    45  
    46  // Message retrieves the stored message content
    47  func (r *SecureReply) Message() []byte {
    48  	r.mu.Lock()
    49  	defer r.mu.Unlock()
    50  
    51  	return []byte(r.MessageBody)
    52  }
    53  
    54  // Valid validates the body of the message by comparing the recorded hash with the hash of the body
    55  func (r *SecureReply) Valid() bool {
    56  	r.mu.Lock()
    57  	defer r.mu.Unlock()
    58  
    59  	hash := r.security.ChecksumBytes([]byte(r.MessageBody))
    60  	if base64.StdEncoding.EncodeToString(hash[:]) == r.Hash {
    61  		validCtr.Inc()
    62  		return true
    63  	}
    64  
    65  	invalidCtr.Inc()
    66  	return false
    67  }
    68  
    69  // JSON creates a JSON encoded reply
    70  func (r *SecureReply) JSON() ([]byte, error) {
    71  	r.mu.Lock()
    72  	j, err := json.Marshal(r)
    73  	r.mu.Unlock()
    74  	if err != nil {
    75  		protocolErrorCtr.Inc()
    76  		return nil, fmt.Errorf("could not JSON Marshal: %s", err)
    77  	}
    78  
    79  	if err = r.IsValidJSON(j); err != nil {
    80  		return nil, fmt.Errorf("reply JSON produced from the SecureRequest does not pass validation: %s", err)
    81  	}
    82  
    83  	return j, nil
    84  }
    85  
    86  // Version retrieves the protocol version for this message
    87  func (r *SecureReply) Version() protocol.ProtocolVersion {
    88  	r.mu.Lock()
    89  	defer r.mu.Unlock()
    90  
    91  	return r.Protocol
    92  }
    93  
    94  // IsValidJSON validates the given JSON data against the schema
    95  func (r *SecureReply) IsValidJSON(data []byte) (err error) {
    96  	if !protocol.ClientStrictValidation {
    97  		return nil
    98  	}
    99  
   100  	_, errors, err := schemaValidate(protocol.SecureReplyV1, data)
   101  	if err != nil {
   102  		return fmt.Errorf("could not validate SecureReply JSON data: %s", err)
   103  	}
   104  
   105  	if len(errors) != 0 {
   106  		return fmt.Errorf("supplied JSON document is not a valid SecureReply message: %s", strings.Join(errors, ", "))
   107  	}
   108  
   109  	return nil
   110  }