acme_client_certificate.c
Go to the documentation of this file.
1 /**
2  * @file acme_client_certificate.c
3  * @brief Certificate management
4  *
5  * @section License
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  *
9  * Copyright (C) 2019-2025 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.5.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_jose.h"
38 #include "acme/acme_client_misc.h"
39 #include "pkix/pem_import.h"
40 #include "encoding/base64url.h"
41 #include "jansson.h"
42 #include "jansson_private.h"
43 #include "debug.h"
44 
45 //Check TCP/IP stack configuration
46 #if (ACME_CLIENT_SUPPORT == ENABLED)
47 
48 
49 /**
50  * @brief Send HTTP request (certificate URL)
51  * @param[in] context Pointer to the ACME client context
52  * @param[out] buffer Pointer to the buffer where to store the certificate chain
53  * @param[in] size Size of the buffer, in bytes
54  * @param[out] length Actual length of the certificate chain, in bytes
55  * @return Error code
56  **/
57 
59  char_t *buffer, size_t size, size_t *length)
60 {
61  error_t error;
62 
63  //Initialize variables
64  error = NO_ERROR;
65 
66  //Perform HTTP request
67  while(!error)
68  {
69  //Check HTTP request state
70  if(context->requestState == ACME_REQ_STATE_INIT)
71  {
72  //Debug message
73  TRACE_DEBUG("\r\n");
74  TRACE_DEBUG("###############################################################################\r\n");
75  TRACE_DEBUG("## DOWNLOAD CERTIFICATE #######################################################\r\n");
76  TRACE_DEBUG("###############################################################################\r\n");
77  TRACE_DEBUG("\r\n");
78 
79  //Update HTTP request state
80  context->requestState = ACME_REQ_STATE_FORMAT_BODY;
81  }
82  else if(context->requestState == ACME_REQ_STATE_FORMAT_BODY)
83  {
84  //Format the body of the HTTP request
85  error = acmeClientFormatDownloadCertRequest(context);
86 
87  //Check status code
88  if(!error)
89  {
90  //Update HTTP request state
91  context->requestState = ACME_REQ_STATE_FORMAT_HEADER;
92  }
93  }
94  else if(context->requestState == ACME_REQ_STATE_FORMAT_HEADER)
95  {
96  //To download the issued certificate, the client simply sends a
97  //POST-as-GET request to the certificate URL (refer to RFC 8555,
98  //section 7.4.2)
99  error = acmeClientFormatRequestHeader(context, "POST",
100  context->order.certificate);
101 
102  //Check status code
103  if(!error)
104  {
105  //Update HTTP request state
106  context->requestState = ACME_REQ_STATE_SEND_HEADER;
107  }
108  }
109  else if(context->requestState == ACME_REQ_STATE_SEND_HEADER ||
110  context->requestState == ACME_REQ_STATE_SEND_BODY ||
111  context->requestState == ACME_REQ_STATE_RECEIVE_HEADER ||
112  context->requestState == ACME_REQ_STATE_PARSE_HEADER ||
113  context->requestState == ACME_REQ_STATE_RECEIVE_BODY ||
114  context->requestState == ACME_REQ_STATE_CLOSE_BODY)
115  {
116  //Perform HTTP request/response transaction
117  error = acmeClientSendRequest(context);
118  }
119  else if(context->requestState == ACME_REQ_STATE_PARSE_BODY)
120  {
121  //Parse the body of the HTTP response
122  error = acmeClientParseDownloadCertResponse(context, buffer, size,
123  length);
124 
125  //The HTTP transaction is complete
126  context->requestState = ACME_REQ_STATE_INIT;
127  break;
128  }
129  else
130  {
131  //Invalid state
132  error = ERROR_WRONG_STATE;
133  }
134  }
135 
136  //Return status code
137  return error;
138 }
139 
140 
141 /**
142  * @brief Format HTTP request body (certificate URL)
143  * @param[in] context Pointer to the ACME client context
144  * @return Error code
145  **/
146 
148 {
149  error_t error;
150  size_t n;
151  char_t *protected;
152  const char_t *payload;
153 
154  //The payload field is empty for POST-as-GET requests
155  payload = "";
156 
157  //Point to the buffer where to format the JWS protected header
158  protected = context->buffer;
159 
160  //Format JWS protected header
161  error = acmeClientFormatJwsProtectedHeader(&context->accountKey,
162  context->account.url, context->nonce, context->order.certificate,
163  protected, &n);
164 
165  //Check status code
166  if(!error)
167  {
168  //Generate the JSON Web Signature
169  error = jwsCreate(context->prngAlgo, context->prngContext, protected,
170  payload, context->accountKey.alg, context->accountKey.privateKey,
171  context->buffer, &context->bufferLen);
172  }
173 
174  //Return status code
175  return error;
176 }
177 
178 
179 /**
180  * @brief Parse HTTP response (certificate URL)
181  * @param[in] context Pointer to the ACME client context
182  * @param[out] buffer Pointer to the buffer where to store the certificate chain
183  * @param[in] size Size of the buffer, in bytes
184  * @param[out] length Actual length of the certificate chain, in bytes
185  * @return Error code
186  **/
187 
189  char_t *buffer, size_t size, size_t *length)
190 {
191  error_t error;
192  size_t n;
193 
194  //Initialize variables
195  error = NO_ERROR;
196 
197  //Check HTTP status code
198  if(!HTTP_STATUS_CODE_2YZ(context->statusCode))
200 
201  //The server must include a Replay-Nonce header field in every successful
202  //response to a POST request (refer to RFC 8555, section 6.5)
203  if(context->nonce[0] == '\0')
204  return ERROR_INVALID_RESPONSE;
205 
206  //Invalid media type?
207  if(osStrcasecmp(context->contentType, "application/pem-certificate-chain") != 0)
208  return ERROR_INVALID_RESPONSE;
209 
210  //Check whether the body of the response is truncated
211  if(context->bufferLen >= ACME_CLIENT_BUFFER_SIZE)
213 
214  //The body must contain a valid PEM certificate chain
215  error = pemImportCertificate(context->buffer, context->bufferLen, NULL,
216  &n, NULL);
217  //Any error to report?
218  if(error)
219  return ERROR_INVALID_RESPONSE;
220 
221  //Make sure the output buffer is large enough to hold the certificate
222  if(context->bufferLen > size)
223  return ERROR_BUFFER_OVERFLOW;
224 
225  //Copy certificate chain
226  osMemcpy(buffer, context->buffer, context->bufferLen);
227 
228  //Return the length of the certificate chain
229  *length = context->bufferLen;
230 
231  //Return status code
232  return error;
233 }
234 
235 
236 /**
237  * @brief Send HTTP request (revokeCert URL)
238  * @param[in] context Pointer to the ACME client context
239  * @param[in] cert Certificate to be revoked (PEM format)
240  * @param[in] certLen Length of the certificate, in bytes
241  * @param[in] privateKey Private key associated with the certificate (PEM
242  * format)
243  * @param[in] privateKeyLen Length of the private key
244  * @param[in] password NULL-terminated string containing the password. This
245  * parameter is required if the private key is encrypted
246  * @param[in] reason Revocation reason code
247  * @return Error code
248  **/
249 
251  const char_t *cert, size_t certLen, const char_t *privateKey,
252  size_t privateKeyLen, const char_t *password, AcmeReasonCode reason)
253 {
254  error_t error;
255 
256  //Initialize variables
257  error = NO_ERROR;
258 
259  //Perform HTTP request
260  while(!error)
261  {
262  //Check HTTP request state
263  if(context->requestState == ACME_REQ_STATE_INIT)
264  {
265  //Debug message
266  TRACE_DEBUG("\r\n");
267  TRACE_DEBUG("###############################################################################\r\n");
268  TRACE_DEBUG("## REVOKE CERTIFICATE #########################################################\r\n");
269  TRACE_DEBUG("###############################################################################\r\n");
270  TRACE_DEBUG("\r\n");
271 
272  //Update HTTP request state
273  context->requestState = ACME_REQ_STATE_FORMAT_BODY;
274  }
275  else if(context->requestState == ACME_REQ_STATE_FORMAT_BODY)
276  {
277  //Format the body of the HTTP request
278  error = acmeClientFormatRevokeCertRequest(context, cert, certLen,
279  privateKey, privateKeyLen, password, reason);
280 
281  //Check status code
282  if(!error)
283  {
284  //Update HTTP request state
285  context->requestState = ACME_REQ_STATE_FORMAT_HEADER;
286  }
287  }
288  else if(context->requestState == ACME_REQ_STATE_FORMAT_HEADER)
289  {
290  //To request that a certificate be revoked, the client sends a POST
291  //request to the ACME server's revokeCert URL (refer to RFC 8555,
292  //section 7.6)
293  error = acmeClientFormatRequestHeader(context, "POST",
294  context->directory.revokeCert);
295 
296  //Check status code
297  if(!error)
298  {
299  //Update HTTP request state
300  context->requestState = ACME_REQ_STATE_SEND_HEADER;
301  }
302  }
303  else if(context->requestState == ACME_REQ_STATE_SEND_HEADER ||
304  context->requestState == ACME_REQ_STATE_SEND_BODY ||
305  context->requestState == ACME_REQ_STATE_RECEIVE_HEADER ||
306  context->requestState == ACME_REQ_STATE_PARSE_HEADER ||
307  context->requestState == ACME_REQ_STATE_RECEIVE_BODY ||
308  context->requestState == ACME_REQ_STATE_CLOSE_BODY)
309  {
310  //Perform HTTP request/response transaction
311  error = acmeClientSendRequest(context);
312  }
313  else if(context->requestState == ACME_REQ_STATE_PARSE_BODY)
314  {
315  //Parse the body of the HTTP response
316  error = acmeClientParseRevokeCertResponse(context);
317 
318  //The HTTP transaction is complete
319  context->requestState = ACME_REQ_STATE_INIT;
320  break;
321  }
322  else
323  {
324  //Invalid state
325  error = ERROR_WRONG_STATE;
326  }
327  }
328 
329  //Return status code
330  return error;
331 }
332 
333 
334 /**
335  * @brief Format HTTP request body (revokeCert URL)
336  * @param[in] context Pointer to the ACME client context
337  * @param[in] cert Certificate to be revoked (PEM format)
338  * @param[in] certLen Length of the certificate, in bytes
339  * @param[in] privateKey Private key associated with the certificate (PEM
340  * format)
341  * @param[in] privateKeyLen Length of the private key
342  * @param[in] password NULL-terminated string containing the password. This
343  * parameter is required if the private key is encrypted
344  * @param[in] reason Revocation reason code
345  * @return Error code
346  **/
347 
349  const char_t *cert, size_t certLen, const char_t *privateKey,
350  size_t privateKeyLen, const char_t *password, AcmeReasonCode reason)
351 {
352  error_t error;
353  int_t ret;
354  size_t n;
355  char_t *protected;
356  char_t *payload;
357  json_t *payloadObj;
358 
359  //Convert the PEM certificate to DER format
360  error = pemImportCertificate(cert, certLen, (uint8_t *) context->buffer,
361  &n, NULL);
362  //Any error to report?
363  if(error)
364  return error;
365 
366  //Encode the DER certificate using Base64url
367  base64urlEncode(context->buffer, n, context->buffer, &n);
368 
369  //Initialize JSON object
370  payloadObj = json_object();
371 
372  //The body of the POST contains the certificate to be revoked
373  ret = json_object_set_new(payloadObj, "certificate",
374  json_string(context->buffer));
375 
376  //The client may include a revocation reason code
377  ret |= json_object_set_new(payloadObj, "reason",
378  json_integer((json_int_t) reason));
379 
380  //JSON object successfully created?
381  if(ret == 0)
382  {
383  //Generate the JSON representation of the payload object
384  payload = json_dumps(payloadObj, JSON_COMPACT);
385  }
386  else
387  {
388  //An error occurred during processing
389  payload = NULL;
390  }
391 
392  //Valid JSON representation?
393  if(payload != NULL)
394  {
395  //Point to the buffer where to format the JWS protected header
396  protected = context->buffer;
397 
398  //Revocation requests are different from other ACME requests in that they
399  //can be signed with either an account key pair or the key pair in the
400  //certificate (refer to RFC 8555, section 7.6)
401  if(privateKey != NULL && privateKeyLen > 0)
402  {
403  AcmeKeyPair certKey;
404 
405  //Load the certificate key pair
406  error = acmeClientLoadCertKeyPair(&certKey, cert, certLen,
407  privateKey, privateKeyLen, password);
408 
409  //Use the certificate key pair for the signature
410  error = acmeClientFormatJwsProtectedHeader(&certKey, NULL,
411  context->nonce, context->directory.revokeCert, protected, &n);
412 
413  //Check status code
414  if(!error)
415  {
416  //Generate the JSON Web Signature
417  error = jwsCreate(context->prngAlgo, context->prngContext,
418  protected, payload, context->accountKey.alg, certKey.privateKey,
419  context->buffer, &context->bufferLen);
420  }
421 
422  //Unload the certificate key pair
423  acmeClientUnloadKeyPair(&certKey);
424  }
425  else
426  {
427  //Use the account key pair for the signature
428  error = acmeClientFormatJwsProtectedHeader(&context->accountKey,
429  context->account.url, context->nonce, context->directory.revokeCert,
430  protected, &n);
431 
432  //Check status code
433  if(!error)
434  {
435  //Generate the JSON Web Signature
436  error = jwsCreate(context->prngAlgo, context->prngContext, protected,
437  payload, context->accountKey.alg, context->accountKey.privateKey,
438  context->buffer, &context->bufferLen);
439  }
440  }
441 
442  //Release JSON string
443  jsonp_free(payload);
444  }
445  else
446  {
447  //Report an error
448  error = ERROR_FAILURE;
449  }
450 
451  //Release JSON object
452  json_decref(payloadObj);
453 
454  //Return status code
455  return error;
456 }
457 
458 
459 /**
460  * @brief Parse HTTP response (certificate URL)
461  * @param[in] context Pointer to the ACME client context
462  * @return Error code
463  **/
464 
466 {
467  //Check HTTP status code
468  if(!HTTP_STATUS_CODE_2YZ(context->statusCode))
470 
471  //The server must include a Replay-Nonce header field in every successful
472  //response to a POST request (refer to RFC 8555, section 6.5)
473  if(context->nonce[0] == '\0')
474  return ERROR_INVALID_RESPONSE;
475 
476  //Successful processing
477  return NO_ERROR;
478 }
479 
480 #endif
error_t acmeClientSendDownloadCertRequest(AcmeClientContext *context, char_t *buffer, size_t size, size_t *length)
Send HTTP request (certificate URL)
signed int int_t
Definition: compiler_port.h:56
@ ERROR_BUFFER_OVERFLOW
Definition: error.h:143
#define HTTP_STATUS_CODE_2YZ(code)
Definition: http_common.h:44
Helper functions for ACME client.
AcmeReasonCode
Revocation reason codes.
Definition: acme_client.h:378
@ ACME_REQ_STATE_PARSE_BODY
Definition: acme_client.h:297
#define ACME_CLIENT_BUFFER_SIZE
Definition: acme_client.h:166
JOSE (JSON Object Signing and Encryption)
@ ERROR_WRONG_STATE
Definition: error.h:210
@ ACME_REQ_STATE_FORMAT_HEADER
Definition: acme_client.h:290
error_t acmeClientSendRequest(AcmeClientContext *context)
Send HTTP request.
error_t pemImportCertificate(const char_t *input, size_t inputLen, uint8_t *output, size_t *outputLen, size_t *consumed)
Decode a PEM file containing a certificate.
Definition: pem_import.c:61
error_t acmeClientLoadCertKeyPair(AcmeKeyPair *keyPair, const char_t *cert, size_t certLen, const char_t *privateKey, size_t privateKeyLen, const char_t *password)
Load certificate/private key pair.
PEM file import functions.
@ ERROR_UNEXPECTED_STATUS
Definition: error.h:284
#define osMemcpy(dest, src, length)
Definition: os_port.h:144
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
Certificate management.
void base64urlEncode(const void *input, size_t inputLen, char_t *output, size_t *outputLen)
Base64url encoding algorithm.
Definition: base64url.c:72
error_t acmeClientParseRevokeCertResponse(AcmeClientContext *context)
Parse HTTP response (certificate URL)
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.
@ ERROR_RESPONSE_TOO_LARGE
Definition: error.h:285
@ ERROR_FAILURE
Generic error code.
Definition: error.h:45
@ ACME_REQ_STATE_CLOSE_BODY
Definition: acme_client.h:298
#define osStrcasecmp(s1, s2)
Definition: os_port.h:186
Base64url encoding scheme.
@ ACME_REQ_STATE_INIT
Definition: acme_client.h:289
error_t acmeClientFormatDownloadCertRequest(AcmeClientContext *context)
Format HTTP request body (certificate URL)
uint8_t length
Definition: tcp.h:375
Public/private key pair.
Definition: acme_client.h:413
@ ACME_REQ_STATE_FORMAT_BODY
Definition: acme_client.h:292
@ ACME_REQ_STATE_RECEIVE_HEADER
Definition: acme_client.h:294
#define TRACE_DEBUG(...)
Definition: debug.h:119
char char_t
Definition: compiler_port.h:55
#define AcmeClientContext
Definition: acme_client.h:248
uint8_t n
uint8_t payload[]
Definition: ipv6.h:286
@ ACME_REQ_STATE_SEND_HEADER
Definition: acme_client.h:291
@ ACME_REQ_STATE_PARSE_HEADER
Definition: acme_client.h:295
error_t acmeClientFormatRevokeCertRequest(AcmeClientContext *context, const char_t *cert, size_t certLen, const char_t *privateKey, size_t privateKeyLen, const char_t *password, AcmeReasonCode reason)
Format HTTP request body (revokeCert URL)
error_t jwsCreate(const PrngAlgo *prngAlgo, void *prngContext, const char_t *protected, const char_t *payload, const char_t *alg, const void *privateKey, char_t *buffer, size_t *written)
Create a JSON Web Signature.
void acmeClientUnloadKeyPair(AcmeKeyPair *keyPair)
Unload public/private key pair.
@ ACME_REQ_STATE_RECEIVE_BODY
Definition: acme_client.h:296
@ ACME_REQ_STATE_SEND_BODY
Definition: acme_client.h:293
const void * privateKey
Definition: acme_client.h:417
@ ERROR_INVALID_RESPONSE
Definition: error.h:71
ACME client (Automatic Certificate Management Environment)
@ NO_ERROR
Success.
Definition: error.h:44
Debugging facilities.
error_t acmeClientParseDownloadCertResponse(AcmeClientContext *context, char_t *buffer, size_t size, size_t *length)
Parse HTTP response (certificate URL)
error_t acmeClientSendRevokeCertRequest(AcmeClientContext *context, const char_t *cert, size_t certLen, const char_t *privateKey, size_t privateKeyLen, const char_t *password, AcmeReasonCode reason)
Send HTTP request (revokeCert URL)