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.2
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
53  * chain (optional parameter)
54  * @param[in] size Size of the buffer, in bytes
55  * @param[out] length Actual length of the certificate chain, in bytes
56  * @return Error code
57  **/
58 
60  char_t *buffer, size_t size, size_t *length)
61 {
62  error_t error;
63 
64  //Initialize variables
65  error = NO_ERROR;
66 
67  //Perform HTTP request
68  while(!error)
69  {
70  //Check HTTP request state
71  if(context->requestState == ACME_REQ_STATE_INIT)
72  {
73  //Debug message
74  TRACE_DEBUG("\r\n");
75  TRACE_DEBUG("###############################################################################\r\n");
76  TRACE_DEBUG("## DOWNLOAD CERTIFICATE #######################################################\r\n");
77  TRACE_DEBUG("###############################################################################\r\n");
78  TRACE_DEBUG("\r\n");
79 
80  //Reset the length of the certificate chain
81  context->certChainLen = 0;
82 
83  //Update HTTP request state
84  context->requestState = ACME_REQ_STATE_FORMAT_BODY;
85  }
86  else if(context->requestState == ACME_REQ_STATE_FORMAT_BODY)
87  {
88  //Format the body of the HTTP request
89  error = acmeClientFormatDownloadCertRequest(context);
90 
91  //Check status code
92  if(!error)
93  {
94  //Update HTTP request state
95  context->requestState = ACME_REQ_STATE_FORMAT_HEADER;
96  }
97  }
98  else if(context->requestState == ACME_REQ_STATE_FORMAT_HEADER)
99  {
100  //To download the issued certificate, the client simply sends a
101  //POST-as-GET request to the certificate URL (refer to RFC 8555,
102  //section 7.4.2)
103  error = acmeClientFormatRequestHeader(context, "POST",
104  context->order.certificate);
105 
106  //Check status code
107  if(!error)
108  {
109  //Update HTTP request state
110  context->requestState = ACME_REQ_STATE_SEND_HEADER;
111  }
112  }
113  else if(context->requestState == ACME_REQ_STATE_SEND_HEADER ||
114  context->requestState == ACME_REQ_STATE_SEND_BODY ||
115  context->requestState == ACME_REQ_STATE_RECEIVE_HEADER ||
116  context->requestState == ACME_REQ_STATE_PARSE_HEADER ||
117  context->requestState == ACME_REQ_STATE_CLOSE_BODY)
118  {
119  //Perform HTTP request/response transaction
120  error = acmeClientSendRequest(context);
121  }
122  else if(context->requestState == ACME_REQ_STATE_RECEIVE_BODY)
123  {
124  //Check HTTP status code
125  if(HTTP_STATUS_CODE_2YZ(context->statusCode))
126  {
127  //Download the certificate chain
128  error = acmeClientReceiveDownloadCertResponse(context, buffer,
129  size, length);
130  }
131  else
132  {
133  //When the server responds with an error status, it should provide
134  //additional information using a problem document
135  error = acmeClientSendRequest(context);
136  }
137  }
138  else if(context->requestState == ACME_REQ_STATE_COMPLETE)
139  {
140  //Parse HTTP response
141  error = acmeClientParseDownloadCertResponse(context, buffer, size,
142  length);
143 
144  //The HTTP transaction is complete
145  context->requestState = ACME_REQ_STATE_INIT;
146  break;
147  }
148  else
149  {
150  //Invalid state
151  error = ERROR_WRONG_STATE;
152  }
153  }
154 
155  //Return status code
156  return error;
157 }
158 
159 
160 /**
161  * @brief Format HTTP request body (certificate URL)
162  * @param[in] context Pointer to the ACME client context
163  * @return Error code
164  **/
165 
167 {
168  error_t error;
169  size_t n;
170  char_t *protected;
171  const char_t *payload;
172 
173  //The payload field is empty for POST-as-GET requests
174  payload = "";
175 
176  //Point to the buffer where to format the JWS protected header
177  protected = context->buffer;
178 
179  //Format JWS protected header
180  error = acmeClientFormatJwsProtectedHeader(&context->accountKey,
181  context->account.url, context->nonce, context->order.certificate,
182  protected, &n);
183 
184  //Check status code
185  if(!error)
186  {
187  //Generate the JSON Web Signature
188  error = jwsCreate(context->prngAlgo, context->prngContext, protected,
189  payload, context->accountKey.alg, context->accountKey.privateKey,
190  context->buffer, &context->bufferLen);
191  }
192 
193  //Return status code
194  return error;
195 }
196 
197 
198 /**
199  * @brief Receive HTTP response (certificate URL)
200  * @param[in] context Pointer to the ACME client context
201  * @param[out] buffer Pointer to the buffer where to store the certificate
202  * chain (optional parameter)
203  * @param[in] size Size of the buffer, in bytes
204  * @param[out] length Actual length of the certificate chain, in bytes
205  * @return Error code
206  **/
207 
209  char_t *buffer, size_t size, size_t *length)
210 {
211  error_t error;
212  size_t n;
213 
214  //Check whether the buffer can hold more data
215  if(buffer != NULL && context->certChainLen < size)
216  {
217  //Receive certificate chain
218  error = httpClientReadBody(&context->httpClientContext,
219  buffer + context->certChainLen, size - context->certChainLen, &n, 0);
220  }
221  else
222  {
223  //Discard extra bytes
224  error = httpClientReadBody(&context->httpClientContext,
225  context->buffer, ACME_CLIENT_BUFFER_SIZE, &n, 0);
226  }
227 
228  //Check status code
229  if(error == NO_ERROR)
230  {
231  //Ajust the length of the certificate chain
232  context->certChainLen += n;
233  }
234  else if(error == ERROR_END_OF_STREAM)
235  {
236  //The end of the response body has been reached
237  error = NO_ERROR;
238 
239  //Update HTTP request state
240  context->requestState = ACME_REQ_STATE_CLOSE_BODY;
241  }
242  else
243  {
244  //Just for sanity
245  }
246 
247  //Return status code
248  return error;
249 }
250 
251 
252 /**
253  * @brief Parse HTTP response (certificate URL)
254  * @param[in] context Pointer to the ACME client context
255  * @param[out] buffer Pointer to the buffer where to store the certificate
256  * chain (optional parameter)
257  * @param[in] size Size of the buffer, in bytes
258  * @param[out] length Actual length of the certificate chain, in bytes
259  * @return Error code
260  **/
261 
263  char_t *buffer, size_t size, size_t *length)
264 {
265  error_t error;
266  size_t n;
267 
268  //Check HTTP status code
269  if(!HTTP_STATUS_CODE_2YZ(context->statusCode))
271 
272  //The server must include a Replay-Nonce header field in every successful
273  //response to a POST request (refer to RFC 8555, section 6.5)
274  if(context->nonce[0] == '\0')
275  return ERROR_INVALID_RESPONSE;
276 
277  //Invalid media type?
278  if(osStrcasecmp(context->contentType, "application/pem-certificate-chain") != 0)
279  return ERROR_INVALID_RESPONSE;
280 
281  //Valid buffer?
282  if(buffer != NULL)
283  {
284  //Make sure the output buffer is large enough to hold the entire
285  //certificate chain
286  if(context->certChainLen > size)
287  return ERROR_BUFFER_OVERFLOW;
288 
289  //The body must contain a valid PEM certificate chain
290  error = pemImportCertificate(buffer, context->certChainLen, NULL, &n,
291  NULL);
292  //Any error to report?
293  if(error)
294  return ERROR_INVALID_RESPONSE;
295  }
296 
297  //Return the actual length of the certificate chain
298  *length = context->certChainLen;
299 
300  //Successful processing
301  return NO_ERROR;
302 }
303 
304 
305 /**
306  * @brief Send HTTP request (revokeCert URL)
307  * @param[in] context Pointer to the ACME client context
308  * @param[in] cert Certificate to be revoked (PEM format)
309  * @param[in] certLen Length of the certificate, in bytes
310  * @param[in] privateKey Private key associated with the certificate (PEM
311  * format)
312  * @param[in] privateKeyLen Length of the private key
313  * @param[in] password NULL-terminated string containing the password. This
314  * parameter is required if the private key is encrypted
315  * @param[in] reason Revocation reason code
316  * @return Error code
317  **/
318 
320  const char_t *cert, size_t certLen, const char_t *privateKey,
321  size_t privateKeyLen, const char_t *password, AcmeReasonCode reason)
322 {
323  error_t error;
324 
325  //Initialize variables
326  error = NO_ERROR;
327 
328  //Perform HTTP request
329  while(!error)
330  {
331  //Check HTTP request state
332  if(context->requestState == ACME_REQ_STATE_INIT)
333  {
334  //Debug message
335  TRACE_DEBUG("\r\n");
336  TRACE_DEBUG("###############################################################################\r\n");
337  TRACE_DEBUG("## REVOKE CERTIFICATE #########################################################\r\n");
338  TRACE_DEBUG("###############################################################################\r\n");
339  TRACE_DEBUG("\r\n");
340 
341  //Update HTTP request state
342  context->requestState = ACME_REQ_STATE_FORMAT_BODY;
343  }
344  else if(context->requestState == ACME_REQ_STATE_FORMAT_BODY)
345  {
346  //Format the body of the HTTP request
347  error = acmeClientFormatRevokeCertRequest(context, cert, certLen,
348  privateKey, privateKeyLen, password, reason);
349 
350  //Check status code
351  if(!error)
352  {
353  //Update HTTP request state
354  context->requestState = ACME_REQ_STATE_FORMAT_HEADER;
355  }
356  }
357  else if(context->requestState == ACME_REQ_STATE_FORMAT_HEADER)
358  {
359  //To request that a certificate be revoked, the client sends a POST
360  //request to the ACME server's revokeCert URL (refer to RFC 8555,
361  //section 7.6)
362  error = acmeClientFormatRequestHeader(context, "POST",
363  context->directory.revokeCert);
364 
365  //Check status code
366  if(!error)
367  {
368  //Update HTTP request state
369  context->requestState = ACME_REQ_STATE_SEND_HEADER;
370  }
371  }
372  else if(context->requestState == ACME_REQ_STATE_SEND_HEADER ||
373  context->requestState == ACME_REQ_STATE_SEND_BODY ||
374  context->requestState == ACME_REQ_STATE_RECEIVE_HEADER ||
375  context->requestState == ACME_REQ_STATE_PARSE_HEADER ||
376  context->requestState == ACME_REQ_STATE_RECEIVE_BODY ||
377  context->requestState == ACME_REQ_STATE_CLOSE_BODY)
378  {
379  //Perform HTTP request/response transaction
380  error = acmeClientSendRequest(context);
381  }
382  else if(context->requestState == ACME_REQ_STATE_COMPLETE)
383  {
384  //Parse the body of the HTTP response
385  error = acmeClientParseRevokeCertResponse(context);
386 
387  //The HTTP transaction is complete
388  context->requestState = ACME_REQ_STATE_INIT;
389  break;
390  }
391  else
392  {
393  //Invalid state
394  error = ERROR_WRONG_STATE;
395  }
396  }
397 
398  //Return status code
399  return error;
400 }
401 
402 
403 /**
404  * @brief Format HTTP request body (revokeCert URL)
405  * @param[in] context Pointer to the ACME client context
406  * @param[in] cert Certificate to be revoked (PEM format)
407  * @param[in] certLen Length of the certificate, in bytes
408  * @param[in] privateKey Private key associated with the certificate (PEM
409  * format)
410  * @param[in] privateKeyLen Length of the private key
411  * @param[in] password NULL-terminated string containing the password. This
412  * parameter is required if the private key is encrypted
413  * @param[in] reason Revocation reason code
414  * @return Error code
415  **/
416 
418  const char_t *cert, size_t certLen, const char_t *privateKey,
419  size_t privateKeyLen, const char_t *password, AcmeReasonCode reason)
420 {
421  error_t error;
422  int_t ret;
423  size_t n;
424  char_t *protected;
425  char_t *payload;
426  json_t *payloadObj;
427 
428  //Convert the PEM certificate to DER format
429  error = pemImportCertificate(cert, certLen, (uint8_t *) context->buffer,
430  &n, NULL);
431  //Any error to report?
432  if(error)
433  return error;
434 
435  //Encode the DER certificate using Base64url
436  base64urlEncode(context->buffer, n, context->buffer, &n);
437 
438  //Initialize JSON object
439  payloadObj = json_object();
440 
441  //The body of the POST contains the certificate to be revoked
442  ret = json_object_set_new(payloadObj, "certificate",
443  json_string(context->buffer));
444 
445  //The client may include a revocation reason code
446  ret |= json_object_set_new(payloadObj, "reason",
447  json_integer((json_int_t) reason));
448 
449  //JSON object successfully created?
450  if(ret == 0)
451  {
452  //Generate the JSON representation of the payload object
453  payload = json_dumps(payloadObj, JSON_COMPACT);
454  }
455  else
456  {
457  //An error occurred during processing
458  payload = NULL;
459  }
460 
461  //Valid JSON representation?
462  if(payload != NULL)
463  {
464  //Point to the buffer where to format the JWS protected header
465  protected = context->buffer;
466 
467  //Revocation requests are different from other ACME requests in that they
468  //can be signed with either an account key pair or the key pair in the
469  //certificate (refer to RFC 8555, section 7.6)
470  if(privateKey != NULL && privateKeyLen > 0)
471  {
472  AcmeKeyPair certKey;
473 
474  //Load the certificate key pair
475  error = acmeClientLoadCertKeyPair(&certKey, cert, certLen,
476  privateKey, privateKeyLen, password);
477 
478  //Use the certificate key pair for the signature
479  error = acmeClientFormatJwsProtectedHeader(&certKey, NULL,
480  context->nonce, context->directory.revokeCert, protected, &n);
481 
482  //Check status code
483  if(!error)
484  {
485  //Generate the JSON Web Signature
486  error = jwsCreate(context->prngAlgo, context->prngContext,
487  protected, payload, context->accountKey.alg, certKey.privateKey,
488  context->buffer, &context->bufferLen);
489  }
490 
491  //Unload the certificate key pair
492  acmeClientUnloadKeyPair(&certKey);
493  }
494  else
495  {
496  //Use the account key pair for the signature
497  error = acmeClientFormatJwsProtectedHeader(&context->accountKey,
498  context->account.url, context->nonce, context->directory.revokeCert,
499  protected, &n);
500 
501  //Check status code
502  if(!error)
503  {
504  //Generate the JSON Web Signature
505  error = jwsCreate(context->prngAlgo, context->prngContext, protected,
506  payload, context->accountKey.alg, context->accountKey.privateKey,
507  context->buffer, &context->bufferLen);
508  }
509  }
510 
511  //Release JSON string
512  jsonp_free(payload);
513  }
514  else
515  {
516  //Report an error
517  error = ERROR_FAILURE;
518  }
519 
520  //Release JSON object
521  json_decref(payloadObj);
522 
523  //Return status code
524  return error;
525 }
526 
527 
528 /**
529  * @brief Parse HTTP response (certificate URL)
530  * @param[in] context Pointer to the ACME client context
531  * @return Error code
532  **/
533 
535 {
536  //Check HTTP status code
537  if(!HTTP_STATUS_CODE_2YZ(context->statusCode))
539 
540  //The server must include a Replay-Nonce header field in every successful
541  //response to a POST request (refer to RFC 8555, section 6.5)
542  if(context->nonce[0] == '\0')
543  return ERROR_INVALID_RESPONSE;
544 
545  //Successful processing
546  return NO_ERROR;
547 }
548 
549 #endif
error_t acmeClientSendDownloadCertRequest(AcmeClientContext *context, char_t *buffer, size_t size, size_t *length)
Send HTTP request (certificate URL)
error_t acmeClientReceiveDownloadCertResponse(AcmeClientContext *context, char_t *buffer, size_t size, size_t *length)
Receive HTTP response (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
error_t httpClientReadBody(HttpClientContext *context, void *data, size_t size, size_t *received, uint_t flags)
Read HTTP response body.
Definition: http_client.c:1648
Helper functions for ACME client.
AcmeReasonCode
Revocation reason codes.
Definition: acme_client.h:378
#define ACME_CLIENT_BUFFER_SIZE
Definition: acme_client.h:166
@ ERROR_END_OF_STREAM
Definition: error.h:211
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:55
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
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_FAILURE
Generic error code.
Definition: error.h:45
@ ACME_REQ_STATE_CLOSE_BODY
Definition: acme_client.h:297
#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)
@ ACME_REQ_STATE_COMPLETE
Definition: acme_client.h:298
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)