acme_client_auth.c
Go to the documentation of this file.
1 /**
2  * @file acme_client_auth.c
3  * @brief Authorization object management
4  *
5  * @section License
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  *
9  * Copyright (C) 2019-2024 Oryx Embedded SARL. All rights reserved.
10  *
11  * This file is part of CycloneACME Open.
12  *
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26  *
27  * @author Oryx Embedded SARL (www.oryx-embedded.com)
28  * @version 2.4.4
29  **/
30 
31 //Switch to the appropriate trace level
32 #define TRACE_LEVEL ACME_TRACE_LEVEL
33 
34 //Dependencies
35 #include "acme/acme_client.h"
36 #include "acme/acme_client_auth.h"
38 #include "acme/acme_client_jose.h"
39 #include "acme/acme_client_misc.h"
40 #include "jansson.h"
41 #include "debug.h"
42 
43 //Check TCP/IP stack configuration
44 #if (ACME_CLIENT_SUPPORT == ENABLED)
45 
46 
47 /**
48  * @brief Send HTTP request (authorization URL)
49  * @param[in] context Pointer to the ACME client context
50  * @param[in] authorization Pointer to the authorization object
51  * @return Error code
52  **/
53 
55  AcmeAuthorization *authorization)
56 {
57  error_t error;
58 
59  //Initialize variables
60  error = NO_ERROR;
61 
62  //Perform HTTP request
63  while(!error)
64  {
65  //Check HTTP request state
66  if(context->requestState == ACME_REQ_STATE_INIT)
67  {
68  //Debug message
69  TRACE_DEBUG("\r\n");
70  TRACE_DEBUG("###############################################################################\r\n");
71  TRACE_DEBUG("## GET AUTHORIZATION ##########################################################\r\n");
72  TRACE_DEBUG("###############################################################################\r\n");
73  TRACE_DEBUG("\r\n");
74 
75  //Update HTTP request state
76  context->requestState = ACME_REQ_STATE_FORMAT_BODY;
77  }
78  else if(context->requestState == ACME_REQ_STATE_FORMAT_BODY)
79  {
80  //Format the body of the HTTP request
81  error = acmeFormatAuthorizationRequest(context, authorization);
82 
83  //Check status code
84  if(!error)
85  {
86  //Update HTTP request state
87  context->requestState = ACME_REQ_STATE_FORMAT_HEADER;
88  }
89  }
90  else if(context->requestState == ACME_REQ_STATE_FORMAT_HEADER)
91  {
92  //When a client receives an order from the server in reply to a
93  //newOrder request, it downloads the authorization resources by
94  //sending POST-as-GET requests to the indicated URLs (refer to
95  //RFC 8555, section 7.5)
96  error = acmeClientFormatRequestHeader(context, "POST",
97  authorization->url);
98 
99  //Check status code
100  if(!error)
101  {
102  //Update HTTP request state
103  context->requestState = ACME_REQ_STATE_SEND_HEADER;
104  }
105  }
106  else if(context->requestState == ACME_REQ_STATE_SEND_HEADER ||
107  context->requestState == ACME_REQ_STATE_SEND_BODY ||
108  context->requestState == ACME_REQ_STATE_RECEIVE_HEADER ||
109  context->requestState == ACME_REQ_STATE_PARSE_HEADER ||
110  context->requestState == ACME_REQ_STATE_RECEIVE_BODY ||
111  context->requestState == ACME_REQ_STATE_CLOSE_BODY)
112  {
113  //Perform HTTP request/response transaction
114  error = acmeClientSendRequest(context);
115  }
116  else if(context->requestState == ACME_REQ_STATE_PARSE_BODY)
117  {
118  //Parse the body of the HTTP response
119  error = acmeClientParseAuthorizationResponse(context, authorization);
120 
121  //The HTTP transaction is complete
122  context->requestState = ACME_REQ_STATE_INIT;
123  break;
124  }
125  else
126  {
127  //Invalid state
128  error = ERROR_WRONG_STATE;
129  }
130  }
131 
132  //Return status code
133  return error;
134 }
135 
136 
137 /**
138  * @brief Format HTTP request body (authorization URL)
139  * @param[in] context Pointer to the ACME client context
140  * @param[in] authorization Pointer to the authorization object
141  * @return Error code
142  **/
143 
145  const AcmeAuthorization *authorization)
146 {
147  error_t error;
148  size_t n;
149  char_t *protected;
150  const char_t *payload;
151 
152  //The payload field is empty for POST-as-GET requests
153  payload = "";
154 
155  //Point to the buffer where to format the JWS protected header
156  protected = context->buffer;
157 
158  //Format JWS protected header
159  error = acmeClientFormatJwsProtectedHeader(&context->accountKey,
160  context->account.url, context->nonce, authorization->url, protected, &n);
161 
162  //Check status code
163  if(!error)
164  {
165  //Generate the JSON Web Signature
166  error = jwsCreate(context->prngAlgo, context->prngContext, protected,
167  payload, context->accountKey.alg, context->accountKey.crv,
168  context->accountKey.privateKey, context->buffer, &context->bufferLen);
169  }
170 
171  //Return status code
172  return error;
173 }
174 
175 
176 /**
177  * @brief Parse HTTP response (authorization URL)
178  * @param[in] context Pointer to the ACME client context
179  * @param[in] authorization Pointer to the authorization object
180  * @return Error code
181  **/
182 
184  AcmeAuthorization *authorization)
185 {
186  error_t error;
187  uint_t i;
188  uint_t n;
189  const char_t *status;
190  const char_t *value;
191  const char_t *type;
192  const char_t *url;
193  const char_t *token;
194  json_t *rootObj;
195  json_t *statusObj;
196  json_t *identifierObj;
197  json_t *valueObj;
198  json_t *wildcardObj;
199  json_t *arrayObj;
200  json_t *challengeObj;
201  json_t *typeObj;
202  json_t *urlObj;
203  json_t *tokenObj;
204  AcmeChallenge *challenge;
205  AcmeChallengeType challengeType;
206 
207  //Check HTTP status code
208  if(!HTTP_STATUS_CODE_2YZ(context->statusCode))
210 
211  //The server must include a Replay-Nonce header field in every successful
212  //response to a POST request (refer to RFC 8555, section 6.5)
213  if(context->nonce[0] == '\0')
214  return ERROR_INVALID_RESPONSE;
215 
216  //Invalid media type?
217  if(osStrcasecmp(context->contentType, "application/json") != 0)
218  return ERROR_INVALID_RESPONSE;
219 
220  //Check whether the body of the response is truncated
221  if(context->bufferLen >= ACME_CLIENT_BUFFER_SIZE)
223 
224  //Initialize status code
225  error = ERROR_INVALID_RESPONSE;
226 
227  //Decode JSON string
228  rootObj = json_loads(context->buffer, 0, NULL);
229 
230  //Start of exception handling block
231  do
232  {
233  //Any parsing error?
234  if(!json_is_object(rootObj))
235  break;
236 
237  //Get "status" object
238  statusObj = json_object_get(rootObj, "status");
239 
240  //The object must be a valid string
241  if(!json_is_string(statusObj))
242  break;
243 
244  //Get the value of the string
245  status = json_string_value(statusObj);
246  //Retrieve the status of the authorization
247  authorization->status = acmeClientParseAuthorizationStatus(status);
248 
249  //Get "identifier" object
250  identifierObj = json_object_get(rootObj, "identifier");
251 
252  //Invalid object?
253  if(!json_is_object(identifierObj))
254  break;
255 
256  //Get "value" object
257  valueObj = json_object_get(identifierObj, "value");
258 
259  //The object must be a valid string
260  if(!json_is_string(valueObj))
261  break;
262 
263  //Get the value of the string
264  value = json_string_value(valueObj);
265 
266  //Check the length of the identifier value
268  break;
269 
270  //Get "wildcard" object
271  wildcardObj = json_object_get(rootObj, "wildcard");
272 
273  //The object is optional
274  if(json_is_boolean(wildcardObj))
275  {
276  //Get the value of the boolean
277  authorization->wildcard = json_boolean_value(wildcardObj);
278  }
279 
280  //Retrieve the challenge validation method
281  challengeType = acmeClientGetChallengeType(context, value,
282  authorization->wildcard);
283 
284  //Check the status of the authorization
285  if(authorization->status == ACME_AUTH_STATUS_PENDING)
286  {
287  //Get "challenges" object
288  arrayObj = json_object_get(rootObj, "challenges");
289 
290  //The object must be a valid array
291  if(!json_is_array(arrayObj))
292  break;
293 
294  //Retrieve the numbers of items in the array
295  n = json_array_size(arrayObj);
296 
297  //Loop through the list of challenges
298  for(i = 0; i < n; i++)
299  {
300  //Point to the current challenge
301  challengeObj = json_array_get(arrayObj, i);
302 
303  //Invalid object?
304  if(!json_is_object(challengeObj))
305  break;
306 
307  //Challenge objects all contain the following basic fields
308  typeObj = json_object_get(challengeObj, "type");
309  urlObj = json_object_get(challengeObj, "url");
310  statusObj = json_object_get(challengeObj, "status");
311 
312  //Invalid challenge object?
313  if(!json_is_string(typeObj) ||
314  !json_is_string(urlObj) ||
315  !json_is_string(statusObj))
316  {
317  break;
318  }
319 
320  //The strings are NULL-terminated
321  type = json_string_value(typeObj);
322  url = json_string_value(urlObj);
323  status = json_string_value(statusObj);
324 
325  //Check challenge type
326  if(acmeClientParseChallengeType(type) == challengeType)
327  {
328  //Additional fields are specified by the challenge type
329  tokenObj = json_object_get(challengeObj, "token");
330 
331  //The object must be a valid string
332  if(!json_is_string(tokenObj))
333  break;
334 
335  //Get the value of the string
336  token = json_string_value(tokenObj);
337 
338  //Valid challenge object?
339  if(osStrlen(url) <= ACME_CLIENT_MAX_URL_LEN &&
342  {
343  //Point to the current challenge
344  challenge = &context->challenges[context->numChallenges];
345 
346  //Retrieve the status of the challenge
347  challenge->status = acmeClientParseChallengeStatus(status);
348  //Save challenge URL
349  osStrcpy(challenge->url, url);
350  //Save token value
351  osStrcpy(challenge->token, token);
352 
353  //Save domain name
354  osStrcpy(challenge->identifier, value);
355  challenge->wildcard = authorization->wildcard;
356 
357  //Save challenge type
358  challenge->type = challengeType;
359 
360  //Generate a key authorization from the "token" value provided
361  //in the challenge and the client's account key
362  error = acmeClientGenerateKeyAuthorization(context, challenge);
363 
364  //Check status code
365  if(!error)
366  {
367  //DNS or TLS-ALPN validation method?
368  if(challenge->type == ACME_CHALLENGE_TYPE_DNS_01)
369  {
370  //The client computes the SHA-256 digest of the key
371  //authorization
372  error = acmeClientDigestKeyAuthorization(context,
373  challenge);
374  }
375  else if(challenge->type == ACME_CHALLENGE_TYPE_TLS_ALPN_01)
376  {
377  //The client prepares for validation by constructing a self-
378  //signed certificate
379  error = acmeClientGenerateTlsAlpnCert(context, challenge);
380  }
381  else
382  {
383  //Just for sanity
384  }
385  }
386 
387  //Check status code
388  if(!error)
389  {
390  //Increment the number of challenges
391  context->numChallenges++;
392  }
393 
394  //Exit immediately
395  break;
396  }
397  }
398  }
399  }
400  else
401  {
402  //Challenge validation is not required since the authorization is not
403  //in "pending" state
404  error = NO_ERROR;
405  }
406 
407  //End of exception handling block
408  } while(0);
409 
410  //Release JSON object
411  json_decref(rootObj);
412 
413  //Return status code
414  return error;
415 }
416 
417 
418 /**
419  * @brief Parse authorization status field
420  * @param[in] label Textual representation of the status
421  * @return Authorization status code
422  **/
423 
425 {
426  AcmeAuthStatus status;
427 
428  //Check the status of the authorization (refer to RFC 8555, section 7.1.6)
429  if(osStrcmp(label, "pending") == 0)
430  {
431  //Authorization objects are created in the "pending" state
432  status = ACME_AUTH_STATUS_PENDING;
433  }
434  else if(osStrcmp(label, "valid") == 0)
435  {
436  //If one of the challenges listed in the authorization transitions to the
437  //"valid" state, then the authorization also changes to the "valid" state
438  status = ACME_AUTH_STATUS_VALID;
439  }
440  else if(osStrcmp(label, "invalid") == 0)
441  {
442  //If the client attempts to fulfill a challenge and fails, or if there is
443  //an error while the authorization is still pending, then the authorization
444  //transitions to the "invalid" state
445  status = ACME_AUTH_STATUS_INVALID;
446  }
447  else if(osStrcmp(label, "expired") == 0)
448  {
449  //A valid authorization can expire
450  status = ACME_AUTH_STATUS_EXPIRED;
451  }
452  else if(osStrcmp(label, "deactivated") == 0)
453  {
454  //An valid authorization can be deactivated by the client
456  }
457  else if(osStrcmp(label, "revoked") == 0)
458  {
459  //An valid authorization can be revoked by the server
460  status = ACME_AUTH_STATUS_REVOKED;
461  }
462  else
463  {
464  //Unknown status
465  status = ACME_AUTH_STATUS_INVALID;
466  }
467 
468  //Return current status
469  return status;
470 }
471 
472 #endif
AcmeChallengeType acmeClientParseChallengeType(const char_t *label)
Parse challenge type field.
#define HTTP_STATUS_CODE_2YZ(code)
Definition: http_common.h:44
Helper functions for ACME client.
@ ACME_AUTH_STATUS_REVOKED
Definition: acme_client.h:342
@ ACME_REQ_STATE_PARSE_BODY
Definition: acme_client.h:297
Challenge object.
Definition: acme_client.h:547
uint8_t type
Definition: coap_common.h:176
@ ACME_CHALLENGE_TYPE_DNS_01
Definition: acme_client.h:368
#define ACME_CLIENT_BUFFER_SIZE
Definition: acme_client.h:166
#define osStrcmp(s1, s2)
Definition: os_port.h:171
AcmeChallengeType acmeClientGetChallengeType(AcmeClientContext *context, const char_t *identifier, bool_t wildcard)
Retrieve the challenge type used for a given domain name.
#define osStrlen(s)
Definition: os_port.h:165
JOSE (JSON Object Signing and Encryption)
@ ACME_AUTH_STATUS_INVALID
Definition: acme_client.h:339
#define ACME_CLIENT_MAX_NAME_LEN
Definition: acme_client.h:173
Challenge object management.
@ ERROR_WRONG_STATE
Definition: error.h:209
@ ACME_AUTH_STATUS_DEACTIVATED
Definition: acme_client.h:341
@ ACME_REQ_STATE_FORMAT_HEADER
Definition: acme_client.h:290
error_t acmeClientSendRequest(AcmeClientContext *context)
Send HTTP request.
AcmeChallengeStatus acmeClientParseChallengeStatus(const char_t *label)
Parse challenge status field.
Authorization object management.
AcmeAuthStatus
Authorization status.
Definition: acme_client.h:335
@ ERROR_UNEXPECTED_STATUS
Definition: error.h:283
error_t acmeClientFormatRequestHeader(AcmeClientContext *context, const char_t *method, const char_t *url)
Format HTTP request header.
error_t
Error codes.
Definition: error.h:43
AcmeChallengeStatus status
Status of the challenge.
Definition: acme_client.h:549
error_t acmeClientFormatJwsProtectedHeader(const AcmeKeyPair *keyPair, const char_t *kid, const char_t *nonce, const char_t *url, char_t *buffer, size_t *written)
Format JWS protected header.
AcmeAuthStatus acmeClientParseAuthorizationStatus(const char_t *label)
Parse authorization status field.
@ ERROR_RESPONSE_TOO_LARGE
Definition: error.h:284
char_t url[ACME_CLIENT_MAX_URL_LEN+1]
Authorization URL.
Definition: acme_client.h:537
@ ACME_REQ_STATE_CLOSE_BODY
Definition: acme_client.h:298
error_t acmeClientDigestKeyAuthorization(AcmeClientContext *context, AcmeChallenge *challenge)
Digest the key authorization (for DNS challenge only)
error_t acmeClientGenerateKeyAuthorization(AcmeClientContext *context, AcmeChallenge *challenge)
Generate key authorization.
error_t acmeClientGenerateTlsAlpnCert(AcmeClientContext *context, AcmeChallenge *challenge)
Generate a self-signed certificate (TLS-ALPN challenge only)
#define osStrcasecmp(s1, s2)
Definition: os_port.h:183
@ ACME_AUTH_STATUS_PENDING
Definition: acme_client.h:337
error_t acmeClientSendAuthorizationRequest(AcmeClientContext *context, AcmeAuthorization *authorization)
Send HTTP request (authorization URL)
@ ACME_AUTH_STATUS_EXPIRED
Definition: acme_client.h:340
@ ACME_REQ_STATE_INIT
Definition: acme_client.h:289
#define ACME_CLIENT_MAX_URL_LEN
Definition: acme_client.h:187
char_t identifier[ACME_CLIENT_MAX_NAME_LEN+1]
Domain name.
Definition: acme_client.h:550
char_t url[ACME_CLIENT_MAX_URL_LEN+1]
Challenge URL.
Definition: acme_client.h:552
Authorization object.
Definition: acme_client.h:535
@ ACME_REQ_STATE_FORMAT_BODY
Definition: acme_client.h:292
@ ACME_REQ_STATE_RECEIVE_HEADER
Definition: acme_client.h:294
@ ACME_AUTH_STATUS_VALID
Definition: acme_client.h:338
#define TRACE_DEBUG(...)
Definition: debug.h:107
char char_t
Definition: compiler_port.h:48
#define AcmeClientContext
Definition: acme_client.h:248
AcmeChallengeType
Challenge types.
Definition: acme_client.h:365
bool_t wildcard
Wildcard domain name.
Definition: acme_client.h:551
error_t acmeFormatAuthorizationRequest(AcmeClientContext *context, const AcmeAuthorization *authorization)
Format HTTP request body (authorization URL)
error_t jwsCreate(const PrngAlgo *prngAlgo, void *prngContext, const char_t *protected, const char_t *payload, const char_t *alg, const char_t *crv, const void *privateKey, char_t *buffer, size_t *written)
Create a JSON Web Signature.
uint8_t n
uint8_t payload[]
Definition: ipv6.h:286
@ ACME_CHALLENGE_TYPE_TLS_ALPN_01
Definition: acme_client.h:369
uint8_t value[]
Definition: tcp.h:369
@ ACME_REQ_STATE_SEND_HEADER
Definition: acme_client.h:291
@ ACME_REQ_STATE_PARSE_HEADER
Definition: acme_client.h:295
error_t acmeClientParseAuthorizationResponse(AcmeClientContext *context, AcmeAuthorization *authorization)
Parse HTTP response (authorization URL)
AcmeAuthStatus status
Status of the authorization.
Definition: acme_client.h:536
@ ACME_REQ_STATE_RECEIVE_BODY
Definition: acme_client.h:296
AcmeChallengeType type
Challenge type.
Definition: acme_client.h:548
unsigned int uint_t
Definition: compiler_port.h:50
@ ACME_REQ_STATE_SEND_BODY
Definition: acme_client.h:293
#define osStrcpy(s1, s2)
Definition: os_port.h:207
@ ERROR_INVALID_RESPONSE
Definition: error.h:71
ACME client (Automatic Certificate Management Environment)
bool_t wildcard
Wildcard domain name.
Definition: acme_client.h:538
@ NO_ERROR
Success.
Definition: error.h:44
Debugging facilities.
uint8_t token[]
Definition: coap_common.h:181
char_t token[ACME_CLIENT_MAX_TOKEN_LEN+1]
Token value.
Definition: acme_client.h:553