1 // Copyright 2023 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 ////////////////////////////////////////////////////////////////////////////////
16
17 //! Implementation of an AuthGraph trusted application (TA).
18
19 use crate::{ag_err, error::Error, keyexchange};
20 use alloc::vec::Vec;
21 use authgraph_wire as wire;
22 use log::{debug, error, trace, warn};
23 use wire::{
24 cbor::AsCborValue, AuthenticationCompleteResponse, Code, CreateResponse, ErrorCode,
25 FinishResponse, InitResponse, PerformOpReq, PerformOpResponse, PerformOpRsp,
26 };
27
28 /// Indication of which roles to support.
29 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
30 pub enum Role {
31 /// Act as (just) a source.
32 Source,
33 /// Act as (just) a sink.
34 Sink,
35 /// Act as both a source and a sink.
36 Both,
37 }
38
39 impl Role {
40 /// Indicate whether role supports acting as a source.
is_source(&self) -> bool41 pub fn is_source(&self) -> bool {
42 match self {
43 Role::Source | Role::Both => true,
44 Role::Sink => false,
45 }
46 }
47 /// Indicate whether role supports acting as a sink.
is_sink(&self) -> bool48 pub fn is_sink(&self) -> bool {
49 match self {
50 Role::Sink | Role::Both => true,
51 Role::Source => false,
52 }
53 }
54 }
55
56 /// AuthGraph trusted application instance.
57 pub struct AuthGraphTa {
58 ag_participant: keyexchange::AuthGraphParticipant,
59 role: Role,
60 }
61
62 impl AuthGraphTa {
63 /// Create a TA instance using the provided trait implementations.
new(ag_participant: keyexchange::AuthGraphParticipant, role: Role) -> Self64 pub fn new(ag_participant: keyexchange::AuthGraphParticipant, role: Role) -> Self {
65 Self { ag_participant, role }
66 }
67
68 /// Process a single serialized request, returning a serialized response.
process(&mut self, req_data: &[u8]) -> Vec<u8>69 pub fn process(&mut self, req_data: &[u8]) -> Vec<u8> {
70 let (req_code, rsp) = match PerformOpReq::from_slice(req_data) {
71 Ok(req) => {
72 trace!("-> TA: received request {:?}", req.code());
73 (Some(req.code()), self.process_req(req))
74 }
75 Err(e) => {
76 error!("failed to decode CBOR request: {:?}", e);
77 (None, error_rsp(ErrorCode::InternalError))
78 }
79 };
80 trace!("<- TA: send response {:?} rc {:?}", req_code, rsp.error_code);
81 match rsp.into_vec() {
82 Ok(rsp_data) => rsp_data,
83 Err(e) => {
84 error!("failed to encode CBOR response: {:?}", e);
85 invalid_cbor_rsp_data().to_vec()
86 }
87 }
88 }
89
90 /// Process a single request, returning a [`PerformOpResponse`].
91 ///
92 /// Select the appropriate method based on the request type, and use the
93 /// request fields as parameters to the method. In the opposite direction,
94 /// build a response message from the values returned by the method.
process_req(&mut self, req: PerformOpReq) -> PerformOpResponse95 fn process_req(&mut self, req: PerformOpReq) -> PerformOpResponse {
96 let code = req.code();
97 let result = match req {
98 PerformOpReq::Create(_req) => {
99 self.create().map(|ret| PerformOpRsp::Create(CreateResponse { ret }))
100 }
101 PerformOpReq::Init(req) => self
102 .init(&req.peer_pub_key, &req.peer_id, &req.peer_nonce, req.peer_version)
103 .map(|ret| PerformOpRsp::Init(InitResponse { ret })),
104 PerformOpReq::Finish(req) => self
105 .finish(
106 &req.peer_pub_key,
107 &req.peer_id,
108 &req.peer_signature,
109 &req.peer_nonce,
110 req.peer_version,
111 req.own_key,
112 )
113 .map(|ret| PerformOpRsp::Finish(FinishResponse { ret })),
114 PerformOpReq::AuthenticationComplete(req) => {
115 self.auth_complete(&req.peer_signature, req.shared_keys).map(|ret| {
116 PerformOpRsp::AuthenticationComplete(AuthenticationCompleteResponse { ret })
117 })
118 }
119 };
120 match result {
121 Ok(rsp) => PerformOpResponse { error_code: ErrorCode::Ok, rsp: Some(rsp) },
122 Err(err) => {
123 warn!("failing {:?} request with error {:?}", code, err);
124 error_rsp(err.0)
125 }
126 }
127 }
128
create(&mut self) -> Result<wire::SessionInitiationInfo, Error>129 fn create(&mut self) -> Result<wire::SessionInitiationInfo, Error> {
130 if !self.role.is_source() {
131 return Err(ag_err!(Unimplemented, "create() invoked on non-source"));
132 }
133 debug!("create()");
134 self.ag_participant.create()
135 }
init( &mut self, peer_pub_key: &[u8], peer_id: &[u8], peer_nonce: &[u8], peer_version: i32, ) -> Result<wire::KeInitResult, Error>136 fn init(
137 &mut self,
138 peer_pub_key: &[u8],
139 peer_id: &[u8],
140 peer_nonce: &[u8],
141 peer_version: i32,
142 ) -> Result<wire::KeInitResult, Error> {
143 if !self.role.is_sink() {
144 return Err(ag_err!(Unimplemented, "init() invoked on non-sink"));
145 }
146 debug!("init()");
147 self.ag_participant.init(peer_pub_key, peer_id, peer_nonce, peer_version)
148 }
finish( &mut self, peer_pub_key: &[u8], peer_id: &[u8], peer_signature: &[u8], peer_nonce: &[u8], peer_version: i32, own_key: wire::Key, ) -> Result<wire::SessionInfo, Error>149 fn finish(
150 &mut self,
151 peer_pub_key: &[u8],
152 peer_id: &[u8],
153 peer_signature: &[u8],
154 peer_nonce: &[u8],
155 peer_version: i32,
156 own_key: wire::Key,
157 ) -> Result<wire::SessionInfo, Error> {
158 if !self.role.is_source() {
159 return Err(ag_err!(Unimplemented, "finish() invoked on non-source"));
160 }
161 debug!("finish()");
162 self.ag_participant.finish(
163 peer_pub_key,
164 peer_id,
165 peer_signature,
166 peer_nonce,
167 peer_version,
168 own_key,
169 )
170 }
auth_complete( &mut self, peer_signature: &[u8], shared_keys: [Vec<u8>; 2], ) -> Result<[Vec<u8>; 2], Error>171 fn auth_complete(
172 &mut self,
173 peer_signature: &[u8],
174 shared_keys: [Vec<u8>; 2],
175 ) -> Result<[Vec<u8>; 2], Error> {
176 if !self.role.is_sink() {
177 return Err(ag_err!(Unimplemented, "auth_complete() invoked on non-sink"));
178 }
179 debug!("auth_complete()");
180 self.ag_participant.authentication_complete(peer_signature, shared_keys)
181 }
182 }
183
184 /// Create an error outer response structure with the given error code.
error_rsp(error_code: ErrorCode) -> PerformOpResponse185 fn error_rsp(error_code: ErrorCode) -> PerformOpResponse {
186 PerformOpResponse { error_code, rsp: None }
187 }
188
189 /// Hand-encoded [`PerformOpResponse`] data for [`ErrorCode::InternalError`].
190 /// Does not perform CBOR serialization (and so is suitable for error reporting if/when
191 /// CBOR serialization fails).
invalid_cbor_rsp_data() -> [u8; 3]192 fn invalid_cbor_rsp_data() -> [u8; 3] {
193 [
194 0x82, // 2-arr
195 0x2b, // nint, value -12
196 0x80, // 0-arr
197 ]
198 }
199
200 #[cfg(test)]
201 mod tests {
202 use super::*;
203
204 #[test]
test_invalid_data()205 fn test_invalid_data() {
206 // Cross-check that the hand-encoded invalid CBOR data matches an auto-encoded equivalent.
207 let rsp = error_rsp(ErrorCode::InternalError);
208 let rsp_data = rsp.into_vec().unwrap();
209 assert_eq!(rsp_data, invalid_cbor_rsp_data());
210 }
211 }
212