acme_client_directory.c
Go to the documentation of this file.
1 /**
2  * @file acme_client_directory.c
3  * @brief Directory 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.0
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"
37 #include "acme/acme_client_misc.h"
38 #include "jansson.h"
39 #include "debug.h"
40 
41 //Check TCP/IP stack configuration
42 #if (ACME_CLIENT_SUPPORT == ENABLED)
43 
44 
45 /**
46  * @brief Send HTTP request (directory URL)
47  * @param[in] context Pointer to the ACME client context
48  * @return Error code
49  **/
50 
52 {
53  error_t error;
54 
55  //Initialize variables
56  error = NO_ERROR;
57 
58  //Perform HTTP request
59  while(!error)
60  {
61  //Check HTTP request state
62  if(context->requestState == ACME_REQ_STATE_INIT)
63  {
64  //Debug message
65  TRACE_DEBUG("\r\n");
66  TRACE_DEBUG("###############################################################################\r\n");
67  TRACE_DEBUG("## GET DIRECTORY ##############################################################\r\n");
68  TRACE_DEBUG("###############################################################################\r\n");
69  TRACE_DEBUG("\r\n");
70 
71  //Check whether the directory object is up-to-date
72  if(context->directory.newNonce[0] != '\0' &&
73  context->directory.newAccount[0] != '\0' &&
74  context->directory.newOrder[0] != '\0' &&
75  context->directory.revokeCert[0] != '\0' &&
76  context->directory.keyChange[0] != '\0')
77  {
78  //The directory contains the most recent information available
79  break;
80  }
81  else
82  {
83  //If the directory object is no longer fresh, the client must access
84  //the directory again by sending a GET request to the directory URL
85  context->requestState = ACME_REQ_STATE_FORMAT_HEADER;
86  }
87  }
88  else if(context->requestState == ACME_REQ_STATE_FORMAT_HEADER)
89  {
90  //Clients access the directory by sending a GET request to the
91  //directory URL (refer to RFC 8555, section 7.1.1)
92  error = acmeClientFormatRequestHeader(context, "GET",
93  context->directoryUri);
94 
95  //Check status code
96  if(!error)
97  {
98  //Update HTTP request state
99  context->requestState = ACME_REQ_STATE_SEND_HEADER;
100  }
101  }
102  else if(context->requestState == ACME_REQ_STATE_SEND_HEADER ||
103  context->requestState == ACME_REQ_STATE_RECEIVE_HEADER ||
104  context->requestState == ACME_REQ_STATE_PARSE_HEADER ||
105  context->requestState == ACME_REQ_STATE_RECEIVE_BODY ||
106  context->requestState == ACME_REQ_STATE_CLOSE_BODY)
107  {
108  //Perform HTTP request/response transaction
109  error = acmeClientSendRequest(context);
110  }
111  else if(context->requestState == ACME_REQ_STATE_PARSE_BODY)
112  {
113  //Parse the body of the HTTP response
114  error = acmeClientParseDirectoryResponse(context);
115 
116  //The HTTP transaction is complete
117  context->requestState = ACME_REQ_STATE_INIT;
118  break;
119  }
120  else
121  {
122  //Invalid state
123  error = ERROR_WRONG_STATE;
124  }
125  }
126 
127  //Return status code
128  return error;
129 }
130 
131 
132 /**
133  * @brief Parse HTTP response (directory URL)
134  * @param[in] context Pointer to the ACME client context
135  * @return Error code
136  **/
137 
139 {
140  error_t error;
141  const char_t *newNonce;
142  const char_t *newAccount;
143  const char_t *newOrder;
144  const char_t *revokeCert;
145  const char_t *keyChange;
146  json_t *rootObj;
147  json_t *newNonceObj;
148  json_t *newAccountObj;
149  json_t *newOrderObj;
150  json_t *revokeCertObj;
151  json_t *keyChangeObj;
152 
153  //Check HTTP status code
154  if(!HTTP_STATUS_CODE_2YZ(context->statusCode))
156 
157  //Invalid media type?
158  if(osStrcasecmp(context->contentType, "application/json"))
159  return ERROR_INVALID_RESPONSE;
160 
161  //Check whether the body of the response is truncated
162  if(context->bufferLen >= ACME_CLIENT_BUFFER_SIZE)
164 
165  //Initialize status code
166  error = ERROR_INVALID_RESPONSE;
167 
168  //Clear directory object
169  osMemset(&context->directory, 0, sizeof(AcmeDirectory));
170 
171  //Decode JSON string
172  rootObj = json_loads(context->buffer, 0, NULL);
173 
174  //Successful parsing?
175  if(json_is_object(rootObj))
176  {
177  //In order to help clients configure themselves with the right URLs for
178  //each ACME operation, ACME servers provide a directory object
179  newNonceObj = json_object_get(rootObj, "newNonce");
180  newAccountObj = json_object_get(rootObj, "newAccount");
181  newOrderObj = json_object_get(rootObj, "newOrder");
182  revokeCertObj = json_object_get(rootObj, "revokeCert");
183  keyChangeObj = json_object_get(rootObj, "keyChange");
184 
185  //Valid directory object?
186  if(json_is_string(newNonceObj) &&
187  json_is_string(newAccountObj) &&
188  json_is_string(newOrderObj) &&
189  json_is_string(revokeCertObj) &&
190  json_is_string(keyChangeObj))
191  {
192  //The strings are NULL-terminated
193  newNonce = json_string_value(newNonceObj);
194  newAccount = json_string_value(newAccountObj);
195  newOrder = json_string_value(newOrderObj);
196  revokeCert = json_string_value(revokeCertObj);
197  keyChange = json_string_value(keyChangeObj);
198 
199  //Check the length of the URLs
200  if(osStrlen(newNonce) <= ACME_CLIENT_MAX_URL_LEN &&
201  osStrlen(newAccount) <= ACME_CLIENT_MAX_URL_LEN &&
202  osStrlen(newOrder) <= ACME_CLIENT_MAX_URL_LEN &&
203  osStrlen(revokeCert) <= ACME_CLIENT_MAX_URL_LEN &&
204  osStrlen(keyChange) <= ACME_CLIENT_MAX_URL_LEN)
205  {
206  //Save the corresponding URLs
207  osStrcpy(context->directory.newNonce, newNonce);
208  osStrcpy(context->directory.newAccount, newAccount);
209  osStrcpy(context->directory.newOrder, newOrder);
210  osStrcpy(context->directory.revokeCert, revokeCert);
211  osStrcpy(context->directory.keyChange, keyChange);
212 
213  //The response was successfully parsed
214  error = NO_ERROR;
215  }
216  }
217  }
218 
219  //Release JSON object
220  json_decref(rootObj);
221 
222  //Return status code
223  return error;
224 }
225 
226 #endif
ACME client (Automatic Certificate Management Environment)
#define ACME_CLIENT_BUFFER_SIZE
Definition: acme_client.h:166
@ ACME_REQ_STATE_PARSE_BODY
Definition: acme_client.h:297
@ ACME_REQ_STATE_RECEIVE_BODY
Definition: acme_client.h:296
@ ACME_REQ_STATE_CLOSE_BODY
Definition: acme_client.h:298
@ ACME_REQ_STATE_INIT
Definition: acme_client.h:289
@ ACME_REQ_STATE_RECEIVE_HEADER
Definition: acme_client.h:294
@ ACME_REQ_STATE_SEND_HEADER
Definition: acme_client.h:291
@ ACME_REQ_STATE_FORMAT_HEADER
Definition: acme_client.h:290
@ ACME_REQ_STATE_PARSE_HEADER
Definition: acme_client.h:295
#define ACME_CLIENT_MAX_URL_LEN
Definition: acme_client.h:187
#define AcmeClientContext
Definition: acme_client.h:248
error_t acmeClientSendDirectoryRequest(AcmeClientContext *context)
Send HTTP request (directory URL)
error_t acmeClientParseDirectoryResponse(AcmeClientContext *context)
Parse HTTP response (directory URL)
Directory object management.
error_t acmeClientSendRequest(AcmeClientContext *context)
Send HTTP request.
error_t acmeClientFormatRequestHeader(AcmeClientContext *context, const char_t *method, const char_t *url)
Format HTTP request header.
Helper functions for ACME client.
char char_t
Definition: compiler_port.h:48
Debugging facilities.
#define TRACE_DEBUG(...)
Definition: debug.h:107
error_t
Error codes.
Definition: error.h:43
@ ERROR_RESPONSE_TOO_LARGE
Definition: error.h:283
@ ERROR_UNEXPECTED_STATUS
Definition: error.h:282
@ NO_ERROR
Success.
Definition: error.h:44
@ ERROR_WRONG_STATE
Definition: error.h:209
@ ERROR_INVALID_RESPONSE
Definition: error.h:71
#define HTTP_STATUS_CODE_2YZ(code)
Definition: http_common.h:44
#define osMemset(p, value, length)
Definition: os_port.h:135
#define osStrcasecmp(s1, s2)
Definition: os_port.h:183
#define osStrlen(s)
Definition: os_port.h:165
#define osStrcpy(s1, s2)
Definition: os_port.h:207
Directory object.
Definition: acme_client.h:486