github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/protocol/v1/constructors.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 "context" 9 "encoding/json" 10 "errors" 11 "fmt" 12 "time" 13 14 "github.com/choria-io/go-choria/inter" 15 "github.com/choria-io/go-choria/protocol" 16 ) 17 18 // NewRequest creates a choria:request:1 19 func NewRequest(agent string, senderid string, callerid string, ttl int, requestid string, collective string) (req protocol.Request, err error) { 20 req = &Request{ 21 Protocol: protocol.RequestV1, 22 Envelope: &RequestEnvelope{ 23 SenderID: senderid, 24 TTL: ttl, 25 RequestID: requestid, 26 Time: time.Now().Unix(), 27 }, 28 } 29 30 req.SetCollective(collective) 31 req.SetAgent(agent) 32 req.SetCallerID(callerid) 33 req.SetFilter(protocol.NewFilter()) 34 35 return req, nil 36 } 37 38 // NewReply creates a choria:reply:1 based on a previous Request 39 func NewReply(request protocol.Request, certname string) (rep protocol.Reply, err error) { 40 if request.Version() != protocol.RequestV1 { 41 return nil, fmt.Errorf("cannot create a version 1 Reply from a %s request", request.Version()) 42 } 43 44 rep = &Reply{ 45 Protocol: protocol.ReplyV1, 46 Envelope: &ReplyEnvelope{ 47 RequestID: request.RequestID(), 48 SenderID: certname, 49 Agent: request.Agent(), 50 Time: time.Now().Unix(), 51 }, 52 } 53 54 protocol.CopyFederationData(request, rep) 55 56 j, err := request.JSON() 57 if err != nil { 58 return nil, fmt.Errorf("could not turn Request %s into a JSON document: %s", request.RequestID(), err) 59 } 60 61 rep.SetMessage(j) 62 63 return rep, nil 64 } 65 66 // NewReplyFromSecureReply create a choria:reply:1 based on the data contained in a SecureReply 67 func NewReplyFromSecureReply(sr protocol.SecureReply) (rep protocol.Reply, err error) { 68 if sr.Version() != protocol.SecureReplyV1 { 69 return nil, fmt.Errorf("cannot create a version 1 SecureReply from a %s SecureReply", sr.Version()) 70 } 71 72 rep = &Reply{ 73 Protocol: protocol.ReplyV1, 74 Envelope: &ReplyEnvelope{}, 75 } 76 77 err = rep.IsValidJSON(sr.Message()) 78 if err != nil { 79 return nil, fmt.Errorf("the JSON body from the SecureReply is not a valid Reply message: %s", err) 80 } 81 82 err = json.Unmarshal(sr.Message(), rep) 83 if err != nil { 84 return nil, fmt.Errorf("could not parse JSON data from Secure Reply: %s", err) 85 } 86 87 return rep, nil 88 } 89 90 // NewRequestFromSecureRequest creates a choria::request:1 based on the data contained in a SecureRequest 91 func NewRequestFromSecureRequest(sr protocol.SecureRequest) (protocol.Request, error) { 92 if sr.Version() != protocol.SecureRequestV1 { 93 return nil, fmt.Errorf("cannot create a version 1 SecureRequest from a %s SecureRequest", sr.Version()) 94 } 95 96 req := &Request{ 97 Protocol: protocol.RequestV1, 98 Envelope: &RequestEnvelope{}, 99 } 100 101 err := req.IsValidJSON(sr.Message()) 102 if err != nil { 103 return nil, fmt.Errorf("the JSON body from the SecureRequest is not a valid Request message: %s", err) 104 } 105 106 err = json.Unmarshal(sr.Message(), req) 107 if err != nil { 108 return nil, fmt.Errorf("could not parse JSON data from Secure Request: %s", err) 109 } 110 111 return req, nil 112 } 113 114 // NewSecureReply creates a choria:secure:reply:1 115 func NewSecureReply(reply protocol.Reply, security inter.SecurityProvider) (secure protocol.SecureReply, err error) { 116 if security.BackingTechnology() != inter.SecurityTechnologyX509 { 117 return nil, fmt.Errorf("version 1 protocol requires a x509 based security system") 118 } 119 120 secure = &SecureReply{ 121 Protocol: protocol.SecureReplyV1, 122 security: security, 123 } 124 125 err = secure.SetMessage(reply) 126 if err != nil { 127 return nil, fmt.Errorf("could not set message on SecureReply structure: %s", err) 128 } 129 130 return secure, nil 131 } 132 133 // NewSecureReplyFromTransport creates a new choria:secure:reply:1 from the data contained in a Transport message 134 func NewSecureReplyFromTransport(message protocol.TransportMessage, security inter.SecurityProvider, skipvalidate bool) (secure protocol.SecureReply, err error) { 135 if security.BackingTechnology() != inter.SecurityTechnologyX509 { 136 return nil, fmt.Errorf("version 1 protocol requires a x509 based security system") 137 } 138 139 secure = &SecureReply{ 140 Protocol: protocol.SecureReplyV1, 141 security: security, 142 } 143 144 data, err := message.Message() 145 if err != nil { 146 return nil, err 147 } 148 149 err = secure.IsValidJSON(data) 150 if err != nil { 151 return nil, fmt.Errorf("the JSON body from the TransportMessage is not a valid SecureReply message: %s", err) 152 } 153 154 err = json.Unmarshal(data, &secure) 155 if err != nil { 156 return nil, err 157 } 158 159 if !skipvalidate { 160 if !secure.Valid() { 161 return nil, errors.New("SecureReply message created from the Transport Message is not valid") 162 } 163 } 164 165 return secure, nil 166 } 167 168 // NewSecureRequest creates a choria:secure:request:1 169 func NewSecureRequest(request protocol.Request, security inter.SecurityProvider) (secure protocol.SecureRequest, err error) { 170 if security.BackingTechnology() != inter.SecurityTechnologyX509 { 171 return nil, fmt.Errorf("version 1 protocol requires a x509 based security system") 172 } 173 174 pub := []byte("insecure") 175 176 if protocol.IsSecure() && !protocol.IsRemoteSignerAgent(request.Agent()) { 177 pub, err = security.PublicCertBytes() 178 if err != nil { 179 // registration when doing anon tls might not have a certificate - so we allow that to go unsigned 180 if protocol.IsRegistrationAgent(request.Agent()) { 181 pub = []byte("insecure registration") 182 } else { 183 return nil, fmt.Errorf("could not retrieve Public Certificate from the security subsystem: %s", err) 184 } 185 } 186 } 187 188 secure = &SecureRequest{ 189 Protocol: protocol.SecureRequestV1, 190 PublicCertificate: string(pub), 191 security: security, 192 } 193 194 err = secure.SetMessage(request) 195 if err != nil { 196 return nil, fmt.Errorf("could not set message SecureRequest structure: %s", err) 197 } 198 199 return secure, nil 200 } 201 202 // NewRemoteSignedSecureRequest is a NewSecureRequest that delegates the signing to a remote signer like aaasvc 203 func NewRemoteSignedSecureRequest(ctx context.Context, request protocol.Request, security inter.SecurityProvider) (secure protocol.SecureRequest, err error) { 204 if security.BackingTechnology() != inter.SecurityTechnologyX509 { 205 return nil, fmt.Errorf("version 1 protocol requires a x509 based security system") 206 } 207 208 // no need for remote stuff, we don't do any signing or certs, 209 // additionally the service hosting the remote signing service isnt 210 // secured by choria protocol since at calling time the client does 211 // not have a cert etc, but the request expects a signed JWT so that 212 // provides the security of that request 213 if !protocol.IsSecure() || protocol.IsRemoteSignerAgent(request.Agent()) { 214 return NewSecureRequest(request, security) 215 } 216 217 reqj, err := request.JSON() 218 if err != nil { 219 return nil, err 220 } 221 222 secj, err := security.RemoteSignRequest(ctx, reqj) 223 if err != nil { 224 return nil, err 225 } 226 227 secure = &SecureRequest{ 228 Protocol: protocol.SecureRequestV1, 229 security: security, 230 } 231 232 err = json.Unmarshal(secj, &secure) 233 if err != nil { 234 return nil, fmt.Errorf("could not parse signed request: %s", err) 235 } 236 237 return secure, nil 238 } 239 240 // NewSecureRequestFromTransport creates a new choria:secure:request:1 from the data contained in a Transport message 241 func NewSecureRequestFromTransport(message protocol.TransportMessage, security inter.SecurityProvider, skipvalidate bool) (secure protocol.SecureRequest, err error) { 242 if security.BackingTechnology() != inter.SecurityTechnologyX509 { 243 return nil, fmt.Errorf("version 1 protocol requires a x509 based security system") 244 } 245 246 secure = &SecureRequest{ 247 security: security, 248 } 249 250 data, err := message.Message() 251 if err != nil { 252 return 253 } 254 255 err = secure.IsValidJSON(data) 256 if err != nil { 257 return nil, fmt.Errorf("the JSON body from the TransportMessage is not a valid SecureRequest message: %s", err) 258 } 259 260 err = json.Unmarshal(data, &secure) 261 if err != nil { 262 return nil, err 263 } 264 265 if !skipvalidate { 266 if !secure.Valid() { 267 return nil, fmt.Errorf("SecureRequest message created from the Transport Message did not pass security validation") 268 } 269 } 270 271 return secure, nil 272 } 273 274 // NewTransportMessage creates a choria:transport:1 275 func NewTransportMessage(certname string) (message protocol.TransportMessage, err error) { 276 message = &TransportMessage{ 277 Protocol: protocol.TransportV1, 278 Headers: &TransportHeaders{}, 279 } 280 281 message.SetSender(certname) 282 283 return message, nil 284 } 285 286 // NewTransportFromJSON creates a new TransportMessage from JSON 287 func NewTransportFromJSON(data []byte) (message protocol.TransportMessage, err error) { 288 message = &TransportMessage{ 289 Headers: &TransportHeaders{}, 290 } 291 292 err = message.IsValidJSON(data) 293 if err != nil { 294 return nil, err 295 } 296 297 err = json.Unmarshal(data, &message) 298 if err != nil { 299 return nil, err 300 } 301 302 return message, nil 303 }