1 /*
2 * LWA auth support for Secure Streams
3 *
4 * libwebsockets - small server side websockets and web server implementation
5 *
6 * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com>
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to
10 * deal in the Software without restriction, including without limitation the
11 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12 * sell copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 * IN THE SOFTWARE.
25 */
26
27 #include <private-lib-core.h>
28
29 typedef struct ss_api_amazon_auth {
30 struct lws_ss_handle *ss;
31 void *opaque_data;
32 /* ... application specific state ... */
33 struct lejp_ctx jctx;
34 lws_sorted_usec_list_t sul;
35 size_t pos;
36 int expires_secs;
37 } ss_api_amazon_auth_t;
38
39 static const char * const lejp_tokens_lwa[] = {
40 "access_token",
41 "expires_in",
42 };
43
44 typedef enum {
45 LSSPPT_ACCESS_TOKEN,
46 LSSPPT_EXPIRES_IN,
47 } lejp_tokens_t;
48
49 enum {
50 AUTH_IDX_LWA,
51 AUTH_IDX_ROOT,
52 };
53
54 static void
lws_ss_sys_auth_api_amazon_com_kick(lws_sorted_usec_list_t * sul)55 lws_ss_sys_auth_api_amazon_com_kick(lws_sorted_usec_list_t *sul)
56 {
57 struct lws_context *context = lws_container_of(sul, struct lws_context,
58 sul_api_amazon_com_kick);
59
60 lws_state_transition_steps(&context->mgr_system,
61 LWS_SYSTATE_OPERATIONAL);
62 }
63
64 static void
lws_ss_sys_auth_api_amazon_com_renew(lws_sorted_usec_list_t * sul)65 lws_ss_sys_auth_api_amazon_com_renew(lws_sorted_usec_list_t *sul)
66 {
67 struct lws_context *context = lws_container_of(sul, struct lws_context,
68 sul_api_amazon_com);
69
70 /* if nothing is there to intercept anything, go all the way */
71 lws_state_transition_steps(&context->mgr_system,
72 LWS_SYSTATE_OPERATIONAL);
73 }
74
75 static signed char
auth_api_amazon_com_parser_cb(struct lejp_ctx * ctx,char reason)76 auth_api_amazon_com_parser_cb(struct lejp_ctx *ctx, char reason)
77 {
78 ss_api_amazon_auth_t *m = (ss_api_amazon_auth_t *)ctx->user;
79 struct lws_context *context = (struct lws_context *)m->opaque_data;
80
81 if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
82 return 0;
83
84 switch (ctx->path_match - 1) {
85 case LSSPPT_ACCESS_TOKEN:
86 if (!ctx->npos)
87 break;
88 if (lws_system_blob_heap_append(lws_system_get_blob(context,
89 LWS_SYSBLOB_TYPE_AUTH,
90 AUTH_IDX_LWA),
91 (const uint8_t *)ctx->buf,
92 ctx->npos)) {
93 lwsl_err("%s: unable to store auth token\n", __func__);
94 return -1;
95 }
96 break;
97 case LSSPPT_EXPIRES_IN:
98 m->expires_secs = atoi(ctx->buf);
99 lws_sul_schedule(context, 0, &context->sul_api_amazon_com,
100 lws_ss_sys_auth_api_amazon_com_renew,
101 (uint64_t)m->expires_secs * LWS_US_PER_SEC);
102 break;
103 }
104
105 return 0;
106 }
107
108 /* secure streams payload interface */
109
110 static int
ss_api_amazon_auth_rx(void * userobj,const uint8_t * buf,size_t len,int flags)111 ss_api_amazon_auth_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
112 {
113 ss_api_amazon_auth_t *m = (ss_api_amazon_auth_t *)userobj;
114 struct lws_context *context = (struct lws_context *)m->opaque_data;
115 lws_system_blob_t *ab;
116 size_t total;
117 int n;
118
119 ab = lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH, AUTH_IDX_LWA);
120
121 if (buf) {
122 if (flags & LWSSS_FLAG_SOM) {
123 lejp_construct(&m->jctx, auth_api_amazon_com_parser_cb,
124 m, lejp_tokens_lwa,
125 LWS_ARRAY_SIZE(lejp_tokens_lwa));
126 lws_system_blob_heap_empty(ab);
127 }
128
129 n = (int)(signed char)lejp_parse(&m->jctx, buf, len);
130 if (n < 0) {
131 lejp_destruct(&m->jctx);
132 lws_system_blob_destroy(
133 lws_system_get_blob(context,
134 LWS_SYSBLOB_TYPE_AUTH,
135 AUTH_IDX_LWA));
136
137 return -1;
138 }
139 }
140 if (!(flags & LWSSS_FLAG_EOM))
141 return 0;
142
143 /* we should have the auth token now */
144
145 total = lws_system_blob_get_size(ab);
146 lwsl_notice("%s: acquired %u-byte api.amazon.com auth token, exp %ds\n",
147 __func__, (unsigned int)total, m->expires_secs);
148
149 lejp_destruct(&m->jctx);
150
151 /* we move the system state at auth connection close */
152
153 return 0;
154 }
155
156 static int
ss_api_amazon_auth_tx(void * userobj,lws_ss_tx_ordinal_t ord,uint8_t * buf,size_t * len,int * flags)157 ss_api_amazon_auth_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,
158 size_t *len, int *flags)
159 {
160 ss_api_amazon_auth_t *m = (ss_api_amazon_auth_t *)userobj;
161 struct lws_context *context = (struct lws_context *)m->opaque_data;
162 lws_system_blob_t *ab;
163 size_t total;
164 int n;
165
166 /*
167 * We send out auth slot AUTH_IDX_ROOT, it's the LWA user / device
168 * identity token
169 */
170
171 ab = lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH, AUTH_IDX_ROOT);
172 total = lws_system_blob_get_size(ab);
173
174 n = lws_system_blob_get(ab, buf, len, m->pos);
175 if (n < 0)
176 return 1;
177
178 if (!m->pos)
179 *flags |= LWSSS_FLAG_SOM;
180
181 m->pos += *len;
182
183 if (m->pos == total) {
184 *flags |= LWSSS_FLAG_EOM;
185 m->pos = 0; /* for next time */
186 }
187
188 return 0;
189 }
190
191 static int
ss_api_amazon_auth_state(void * userobj,void * sh,lws_ss_constate_t state,lws_ss_tx_ordinal_t ack)192 ss_api_amazon_auth_state(void *userobj, void *sh, lws_ss_constate_t state,
193 lws_ss_tx_ordinal_t ack)
194 {
195 ss_api_amazon_auth_t *m = (ss_api_amazon_auth_t *)userobj;
196 struct lws_context *context = (struct lws_context *)m->opaque_data;
197 size_t s;
198
199 lwsl_info("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state),
200 (unsigned int)ack);
201
202 switch (state) {
203 case LWSSSCS_CREATING:
204 case LWSSSCS_CONNECTING:
205 s = lws_system_blob_get_size(
206 lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH,
207 AUTH_IDX_ROOT));
208 lws_ss_request_tx_len(m->ss, s);
209 m->pos = 0;
210 break;
211
212 case LWSSSCS_DISCONNECTED:
213 /*
214 * We defer moving the system state forward until we have
215 * closed our connection + tls for the auth action... this is
216 * because on small systems, we need that memory recovered
217 * before we can make another connection subsequently.
218 *
219 * At this point, we're ultimately being called from within
220 * the wsi close process, the tls tunnel is not freed yet.
221 * Use a sul to actually do it next time around the event loop
222 * when the close process for the auth wsi has completed and
223 * the related tls is already freed.
224 */
225 s = lws_system_blob_get_size(
226 lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH,
227 AUTH_IDX_LWA));
228
229 if (s)
230 lws_sul_schedule(context, 0,
231 &context->sul_api_amazon_com_kick,
232 lws_ss_sys_auth_api_amazon_com_kick, 1);
233 break;
234
235 case LWSSSCS_DESTROYING:
236 lws_sul_schedule(context, 0, &context->sul_api_amazon_com,
237 NULL, LWS_SET_TIMER_USEC_CANCEL);
238 lws_system_blob_destroy(
239 lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH,
240 AUTH_IDX_LWA));
241 break;
242
243 default:
244 break;
245 }
246
247 return 0;
248 }
249
250 int
lws_ss_sys_auth_api_amazon_com(struct lws_context * context)251 lws_ss_sys_auth_api_amazon_com(struct lws_context *context)
252 {
253 lws_ss_info_t ssi;
254
255 if (context->hss_auth) /* already exists */
256 return 0;
257
258 /* We're making an outgoing secure stream ourselves */
259
260 memset(&ssi, 0, sizeof(ssi));
261 ssi.handle_offset = offsetof(ss_api_amazon_auth_t, ss);
262 ssi.opaque_user_data_offset = offsetof(ss_api_amazon_auth_t, opaque_data);
263 ssi.rx = ss_api_amazon_auth_rx;
264 ssi.tx = ss_api_amazon_auth_tx;
265 ssi.state = ss_api_amazon_auth_state;
266 ssi.user_alloc = sizeof(ss_api_amazon_auth_t);
267 ssi.streamtype = "api_amazon_com_auth";
268
269 if (lws_ss_create(context, 0, &ssi, context, &context->hss_auth,
270 NULL, NULL)) {
271 lwsl_info("%s: Create LWA auth ss failed (policy?)\n", __func__);
272 return 1;
273 }
274
275 return 0;
276 }
277