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 }