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 size_t pos;
35 int expires_secs;
36 } ss_api_amazon_auth_t;
37
38 static const char * const lejp_tokens_lwa[] = {
39 "access_token",
40 "expires_in",
41 };
42
43 typedef enum {
44 LSSPPT_ACCESS_TOKEN,
45 LSSPPT_EXPIRES_IN,
46 } lejp_tokens_t;
47
48 enum {
49 AUTH_IDX_LWA,
50 AUTH_IDX_ROOT,
51 };
52
53 static void
lws_ss_sys_auth_api_amazon_com_kick(lws_sorted_usec_list_t * sul)54 lws_ss_sys_auth_api_amazon_com_kick(lws_sorted_usec_list_t *sul)
55 {
56 struct lws_context *context = lws_container_of(sul, struct lws_context,
57 sul_api_amazon_com_kick);
58
59 lws_state_transition_steps(&context->mgr_system,
60 LWS_SYSTATE_OPERATIONAL);
61 }
62
63 static void
lws_ss_sys_auth_api_amazon_com_renew(lws_sorted_usec_list_t * sul)64 lws_ss_sys_auth_api_amazon_com_renew(lws_sorted_usec_list_t *sul)
65 {
66 struct lws_context *context = lws_container_of(sul, struct lws_context,
67 sul_api_amazon_com);
68
69 lws_ss_sys_auth_api_amazon_com(context);
70 }
71
72 static signed char
auth_api_amazon_com_parser_cb(struct lejp_ctx * ctx,char reason)73 auth_api_amazon_com_parser_cb(struct lejp_ctx *ctx, char reason)
74 {
75 ss_api_amazon_auth_t *m = (ss_api_amazon_auth_t *)ctx->user;
76 struct lws_context *context = (struct lws_context *)m->opaque_data;
77 lws_system_blob_t *blob;
78
79 if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
80 return 0;
81
82 switch (ctx->path_match - 1) {
83 case LSSPPT_ACCESS_TOKEN:
84 if (!ctx->npos)
85 break;
86
87 blob = lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH,
88 AUTH_IDX_LWA);
89 if (!blob)
90 return -1;
91
92 if (lws_system_blob_heap_append(blob,
93 (const uint8_t *)ctx->buf,
94 ctx->npos)) {
95 lwsl_err("%s: unable to store auth token\n", __func__);
96
97 return -1;
98 }
99 break;
100 case LSSPPT_EXPIRES_IN:
101 m->expires_secs = atoi(ctx->buf);
102 lws_sul_schedule(context, 0, &context->sul_api_amazon_com,
103 lws_ss_sys_auth_api_amazon_com_renew,
104 (lws_usec_t)m->expires_secs * LWS_US_PER_SEC);
105 break;
106 }
107
108 return 0;
109 }
110
111 /* secure streams payload interface */
112
113 static lws_ss_state_return_t
ss_api_amazon_auth_rx(void * userobj,const uint8_t * buf,size_t len,int flags)114 ss_api_amazon_auth_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
115 {
116 ss_api_amazon_auth_t *m = (ss_api_amazon_auth_t *)userobj;
117 struct lws_context *context = (struct lws_context *)m->opaque_data;
118 lws_system_blob_t *ab;
119 #if !defined(LWS_WITH_NO_LOGS)
120 size_t total;
121 #endif
122 int n;
123
124 ab = lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH, AUTH_IDX_LWA);
125 /* coverity */
126 if (!ab)
127 return LWSSSSRET_DISCONNECT_ME;
128
129 if (buf) {
130 if (flags & LWSSS_FLAG_SOM) {
131 lejp_construct(&m->jctx, auth_api_amazon_com_parser_cb,
132 m, lejp_tokens_lwa,
133 LWS_ARRAY_SIZE(lejp_tokens_lwa));
134 lws_system_blob_heap_empty(ab);
135 }
136
137 n = lejp_parse(&m->jctx, buf, (int)len);
138 if (n < 0) {
139 lejp_destruct(&m->jctx);
140 lws_system_blob_destroy(
141 lws_system_get_blob(context,
142 LWS_SYSBLOB_TYPE_AUTH,
143 AUTH_IDX_LWA));
144
145 return LWSSSSRET_DISCONNECT_ME;
146 }
147 }
148 if (!(flags & LWSSS_FLAG_EOM))
149 return LWSSSSRET_OK;
150
151 /* we should have the auth token now */
152
153 #if !defined(LWS_WITH_NO_LOGS)
154 total = lws_system_blob_get_size(ab);
155 lwsl_notice("%s: acquired %u-byte api.amazon.com auth token, exp %ds\n",
156 __func__, (unsigned int)total, m->expires_secs);
157 #endif
158
159 lejp_destruct(&m->jctx);
160
161 /* we move the system state at auth connection close */
162
163 return LWSSSSRET_DISCONNECT_ME;
164 }
165
166 static lws_ss_state_return_t
ss_api_amazon_auth_tx(void * userobj,lws_ss_tx_ordinal_t ord,uint8_t * buf,size_t * len,int * flags)167 ss_api_amazon_auth_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,
168 size_t *len, int *flags)
169 {
170 ss_api_amazon_auth_t *m = (ss_api_amazon_auth_t *)userobj;
171 struct lws_context *context = (struct lws_context *)m->opaque_data;
172 lws_system_blob_t *ab;
173 size_t total;
174 int n;
175
176 /*
177 * We send out auth slot AUTH_IDX_ROOT, it's the LWA user / device
178 * identity token
179 */
180
181 ab = lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH, AUTH_IDX_ROOT);
182 if (!ab)
183 return LWSSSSRET_DESTROY_ME;
184
185 total = lws_system_blob_get_size(ab);
186
187 n = lws_system_blob_get(ab, buf, len, m->pos);
188 if (n < 0)
189 return LWSSSSRET_TX_DONT_SEND;
190
191 if (!m->pos)
192 *flags |= LWSSS_FLAG_SOM;
193
194 m->pos += *len;
195
196 if (m->pos == total) {
197 *flags |= LWSSS_FLAG_EOM;
198 m->pos = 0; /* for next time */
199 }
200
201 return LWSSSSRET_OK;
202 }
203
204 static lws_ss_state_return_t
ss_api_amazon_auth_state(void * userobj,void * sh,lws_ss_constate_t state,lws_ss_tx_ordinal_t ack)205 ss_api_amazon_auth_state(void *userobj, void *sh, lws_ss_constate_t state,
206 lws_ss_tx_ordinal_t ack)
207 {
208 ss_api_amazon_auth_t *m = (ss_api_amazon_auth_t *)userobj;
209 struct lws_context *context = (struct lws_context *)m->opaque_data;
210 lws_system_blob_t *ab;
211 size_t s;
212
213 lwsl_info("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name((int)state),
214 (unsigned int)ack);
215
216 ab = lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH, AUTH_IDX_ROOT);
217 /* coverity */
218 if (!ab)
219 return LWSSSSRET_DESTROY_ME;
220
221 switch (state) {
222 case LWSSSCS_CREATING:
223 //if (lws_ss_set_metadata(m->ss, "ctype", "application/json", 16))
224 // return LWSSSSRET_DESTROY_ME;
225 /* fallthru */
226 case LWSSSCS_CONNECTING:
227 s = lws_system_blob_get_size(ab);
228 if (!s)
229 lwsl_debug("%s: no auth blob\n", __func__);
230 m->pos = 0;
231 return lws_ss_request_tx_len(m->ss, (unsigned long)s);
232
233 case LWSSSCS_DISCONNECTED:
234 /*
235 * We defer moving the system state forward until we have
236 * closed our connection + tls for the auth action... this is
237 * because on small systems, we need that memory recovered
238 * before we can make another connection subsequently.
239 *
240 * At this point, we're ultimately being called from within
241 * the wsi close process, the tls tunnel is not freed yet.
242 * Use a sul to actually do it next time around the event loop
243 * when the close process for the auth wsi has completed and
244 * the related tls is already freed.
245 */
246 s = lws_system_blob_get_size(ab);
247
248 if (s && context->mgr_system.state != LWS_SYSTATE_OPERATIONAL)
249 lws_sul_schedule(context, 0,
250 &context->sul_api_amazon_com_kick,
251 lws_ss_sys_auth_api_amazon_com_kick, 1);
252
253 context->hss_auth = NULL;
254 return LWSSSSRET_DESTROY_ME;
255
256 default:
257 break;
258 }
259
260 return LWSSSSRET_OK;
261 }
262
263 int
lws_ss_sys_auth_api_amazon_com(struct lws_context * context)264 lws_ss_sys_auth_api_amazon_com(struct lws_context *context)
265 {
266 lws_ss_info_t ssi;
267
268 if (context->hss_auth) /* already exists */
269 return 0;
270
271 /* We're making an outgoing secure stream ourselves */
272
273 memset(&ssi, 0, sizeof(ssi));
274 ssi.handle_offset = offsetof(ss_api_amazon_auth_t, ss);
275 ssi.opaque_user_data_offset = offsetof(ss_api_amazon_auth_t, opaque_data);
276 ssi.rx = ss_api_amazon_auth_rx;
277 ssi.tx = ss_api_amazon_auth_tx;
278 ssi.state = ss_api_amazon_auth_state;
279 ssi.user_alloc = sizeof(ss_api_amazon_auth_t);
280 ssi.streamtype = "api_amazon_com_auth";
281
282 if (lws_ss_create(context, 0, &ssi, context, &context->hss_auth,
283 NULL, NULL)) {
284 lwsl_info("%s: Create LWA auth ss failed (policy?)\n", __func__);
285 return 1;
286 }
287
288 return 0;
289 }
290