LCOV - code coverage report
Current view: top level - stuffer - s2n_stuffer_pem.c (source / functions) Hit Total Coverage
Test: unit_test_coverage.info Lines: 110 110 100.0 %
Date: 2025-08-15 07:28:39 Functions: 11 11 100.0 %
Branches: 72 114 63.2 %

           Branch data     Line data    Source code
       1                 :            : /*
       2                 :            :  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
       3                 :            :  *
       4                 :            :  * Licensed under the Apache License, Version 2.0 (the "License").
       5                 :            :  * You may not use this file except in compliance with the License.
       6                 :            :  * A copy of the License is located at
       7                 :            :  *
       8                 :            :  *  http://aws.amazon.com/apache2.0
       9                 :            :  *
      10                 :            :  * or in the "license" file accompanying this file. This file is distributed
      11                 :            :  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
      12                 :            :  * express or implied. See the License for the specific language governing
      13                 :            :  * permissions and limitations under the License.
      14                 :            :  */
      15                 :            : 
      16                 :            : #include <openssl/evp.h>
      17                 :            : #include <string.h>
      18                 :            : 
      19                 :            : #include "error/s2n_errno.h"
      20                 :            : #include "stuffer/s2n_stuffer.h"
      21                 :            : #include "utils/s2n_safety.h"
      22                 :            : 
      23                 :            : #define S2N_PEM_DELIMITER_CHAR        '-'
      24                 :            : #define S2N_PEM_DELIMITER_TOKEN       "--"
      25                 :            : #define S2N_PEM_DELIMITER_MIN_COUNT   2
      26                 :            : #define S2N_PEM_DELIMITER_MAX_COUNT   64
      27                 :       6410 : #define S2N_PEM_BEGIN_TOKEN           "BEGIN "
      28                 :      12976 : #define S2N_PEM_END_TOKEN             "END "
      29                 :        553 : #define S2N_PEM_PKCS1_RSA_PRIVATE_KEY "RSA PRIVATE KEY"
      30                 :        501 : #define S2N_PEM_PKCS1_EC_PRIVATE_KEY  "EC PRIVATE KEY"
      31                 :        349 : #define S2N_PEM_PKCS8_PRIVATE_KEY     "PRIVATE KEY"
      32                 :         83 : #define S2N_PEM_DH_PARAMETERS         "DH PARAMETERS"
      33                 :        501 : #define S2N_PEM_EC_PARAMETERS         "EC PARAMETERS"
      34                 :       1824 : #define S2N_PEM_CERTIFICATE           "CERTIFICATE"
      35                 :         10 : #define S2N_PEM_CRL                   "X509 CRL"
      36                 :            : 
      37                 :            : static S2N_RESULT s2n_stuffer_pem_read_delimiter_chars(struct s2n_stuffer *pem)
      38                 :       8624 : {
      39 [ -  + ][ #  # ]:       8624 :     RESULT_ENSURE_REF(pem);
      40 [ +  - ][ +  + ]:       8624 :     RESULT_ENSURE(s2n_stuffer_data_available(pem) >= S2N_PEM_DELIMITER_MIN_COUNT,
      41                 :       7961 :             S2N_ERR_INVALID_PEM);
      42                 :            : 
      43                 :            :     /* Skip any number of chars until a "--" is reached.
      44                 :            :      * We use "--" instead of "-" to account for dashes that appear in comments.
      45                 :            :      * We do not accept comments that contain "--".
      46                 :            :      */
      47         [ +  + ]:       7961 :     RESULT_GUARD_POSIX(s2n_stuffer_skip_read_until(pem, S2N_PEM_DELIMITER_TOKEN));
      48         [ -  + ]:       7959 :     RESULT_GUARD_POSIX(s2n_stuffer_rewind_read(pem, strlen(S2N_PEM_DELIMITER_TOKEN)));
      49                 :            : 
      50                 :            :     /* Ensure between 2 and 64 '-' chars at start of line. */
      51         [ -  + ]:       7959 :     RESULT_GUARD_POSIX(s2n_stuffer_skip_expected_char(pem, S2N_PEM_DELIMITER_CHAR,
      52                 :       7959 :             S2N_PEM_DELIMITER_MIN_COUNT, S2N_PEM_DELIMITER_MAX_COUNT, NULL));
      53                 :            : 
      54                 :       7959 :     return S2N_RESULT_OK;
      55                 :       7959 : }
      56                 :            : 
      57                 :            : static int s2n_stuffer_pem_read_encapsulation_line(struct s2n_stuffer *pem, const char *encap_marker,
      58                 :            :         const char *keyword)
      59                 :       6415 : {
      60         [ +  + ]:       6415 :     POSIX_GUARD_RESULT(s2n_stuffer_pem_read_delimiter_chars(pem));
      61                 :            : 
      62                 :            :     /* Ensure next string in stuffer is "BEGIN " or "END " */
      63         [ +  + ]:       6414 :     POSIX_GUARD(s2n_stuffer_read_expected_str(pem, encap_marker));
      64                 :            : 
      65                 :            :     /* Ensure next string is stuffer is the keyword (Eg "CERTIFICATE", "PRIVATE KEY", etc) */
      66         [ +  + ]:       6399 :     POSIX_GUARD(s2n_stuffer_read_expected_str(pem, keyword));
      67                 :            : 
      68                 :            :     /* Ensure between 2 and 64 '-' chars at end of line */
      69         [ +  + ]:       5195 :     POSIX_GUARD(s2n_stuffer_skip_expected_char(pem, S2N_PEM_DELIMITER_CHAR, S2N_PEM_DELIMITER_MIN_COUNT,
      70                 :       5191 :             S2N_PEM_DELIMITER_MAX_COUNT, NULL));
      71                 :            : 
      72                 :            :     /* Check for missing newline between dashes case: "-----END CERTIFICATE----------BEGIN CERTIFICATE-----" */
      73         [ +  + ]:       5191 :     if (strncmp(encap_marker, S2N_PEM_END_TOKEN, strlen(S2N_PEM_END_TOKEN)) == 0
      74         [ +  + ]:       5191 :             && s2n_stuffer_peek_check_for_str(pem, S2N_PEM_BEGIN_TOKEN) == S2N_SUCCESS) {
      75                 :            :         /* Rewind stuffer by 2 bytes before BEGIN, so that next read will find the dashes before the BEGIN */
      76         [ -  + ]:          7 :         POSIX_GUARD(s2n_stuffer_rewind_read(pem, S2N_PEM_DELIMITER_MIN_COUNT));
      77                 :          7 :     }
      78                 :            : 
      79                 :            :     /* Skip newlines and other whitespace that may be after the dashes */
      80                 :       5191 :     return s2n_stuffer_skip_whitespace(pem, NULL);
      81                 :       5191 : }
      82                 :            : 
      83                 :            : static int s2n_stuffer_pem_read_begin(struct s2n_stuffer *pem, const char *keyword)
      84                 :       3821 : {
      85                 :       3821 :     return s2n_stuffer_pem_read_encapsulation_line(pem, S2N_PEM_BEGIN_TOKEN, keyword);
      86                 :       3821 : }
      87                 :            : 
      88                 :            : static int s2n_stuffer_pem_read_end(struct s2n_stuffer *pem, const char *keyword)
      89                 :       2594 : {
      90                 :       2594 :     return s2n_stuffer_pem_read_encapsulation_line(pem, S2N_PEM_END_TOKEN, keyword);
      91                 :       2594 : }
      92                 :            : 
      93                 :            : static int s2n_stuffer_pem_read_contents(struct s2n_stuffer *pem, struct s2n_stuffer *asn1)
      94                 :       2602 : {
      95 [ -  + ][ #  # ]:       2602 :     s2n_stack_blob(base64__blob, 64, 64);
                 [ -  + ]
      96                 :       2602 :     struct s2n_stuffer base64_stuffer = { 0 };
      97         [ -  + ]:       2602 :     POSIX_GUARD(s2n_stuffer_init(&base64_stuffer, &base64__blob));
      98                 :            : 
      99                 :    2768492 :     while (1) {
     100                 :            :         /* We need a byte... */
     101 [ +  + ][ +  - ]:    2768492 :         POSIX_ENSURE(s2n_stuffer_data_available(pem) >= 1, S2N_ERR_STUFFER_OUT_OF_DATA);
     102                 :            : 
     103                 :            :         /* Peek to see if the next char is a dash, meaning end of pem_contents */
     104                 :    2768484 :         uint8_t c = pem->blob.data[pem->read_cursor];
     105         [ +  + ]:    2768484 :         if (c == '-') {
     106                 :       2594 :             break;
     107                 :       2594 :         }
     108                 :            :         /* Else, move read pointer forward by 1 byte since we will be consuming it. */
     109                 :    2765890 :         pem->read_cursor += 1;
     110                 :            : 
     111                 :            :         /* Skip non-base64 characters */
     112         [ +  + ]:    2765890 :         if (!s2n_is_base64_char(c)) {
     113                 :      42595 :             continue;
     114                 :      42595 :         }
     115                 :            : 
     116                 :            :         /* Flush base64_stuffer to asn1 stuffer if we're out of space, and reset base64_stuffer read/write pointers */
     117         [ +  + ]:    2723295 :         if (s2n_stuffer_space_remaining(&base64_stuffer) == 0) {
     118         [ -  + ]:      41119 :             POSIX_GUARD(s2n_stuffer_read_base64(&base64_stuffer, asn1));
     119         [ -  + ]:      41119 :             POSIX_GUARD(s2n_stuffer_rewrite(&base64_stuffer));
     120                 :      41119 :         }
     121                 :            : 
     122                 :            :         /* Copy next char to base64_stuffer */
     123         [ -  + ]:    2723295 :         POSIX_GUARD(s2n_stuffer_write_bytes(&base64_stuffer, (uint8_t *) &c, 1));
     124                 :    2723295 :     };
     125                 :            : 
     126                 :            :     /* Flush any remaining bytes to asn1 */
     127         [ -  + ]:       2594 :     POSIX_GUARD(s2n_stuffer_read_base64(&base64_stuffer, asn1));
     128                 :            : 
     129                 :       2594 :     return S2N_SUCCESS;
     130                 :       2594 : }
     131                 :            : 
     132                 :            : static int s2n_stuffer_data_from_pem(struct s2n_stuffer *pem, struct s2n_stuffer *asn1, const char *keyword)
     133                 :       3821 : {
     134 [ -  + ][ +  - ]:       3821 :     POSIX_PRECONDITION(s2n_stuffer_validate(pem));
     135 [ -  + ][ +  - ]:       3821 :     POSIX_PRECONDITION(s2n_stuffer_validate(asn1));
     136 [ #  # ][ -  + ]:       3821 :     POSIX_ENSURE_REF(keyword);
     137                 :            : 
     138         [ +  + ]:       3821 :     POSIX_GUARD(s2n_stuffer_pem_read_begin(pem, keyword));
     139         [ +  + ]:       2602 :     POSIX_GUARD(s2n_stuffer_pem_read_contents(pem, asn1));
     140         [ +  + ]:       2594 :     POSIX_GUARD(s2n_stuffer_pem_read_end(pem, keyword));
     141                 :            : 
     142 [ -  + ][ +  - ]:       2589 :     POSIX_POSTCONDITION(s2n_stuffer_validate(pem));
     143 [ -  + ][ +  - ]:       2589 :     POSIX_POSTCONDITION(s2n_stuffer_validate(asn1));
     144                 :       2589 :     return S2N_SUCCESS;
     145                 :       2589 : }
     146                 :            : 
     147                 :            : int s2n_stuffer_private_key_from_pem(struct s2n_stuffer *pem, struct s2n_stuffer *asn1, int *type_hint)
     148                 :        553 : {
     149 [ -  + ][ +  - ]:        553 :     POSIX_PRECONDITION(s2n_stuffer_validate(pem));
     150 [ -  + ][ +  - ]:        553 :     POSIX_PRECONDITION(s2n_stuffer_validate(asn1));
     151 [ -  + ][ #  # ]:        553 :     POSIX_ENSURE_REF(type_hint);
     152                 :            : 
     153         [ +  + ]:        553 :     if (s2n_stuffer_data_from_pem(pem, asn1, S2N_PEM_PKCS1_RSA_PRIVATE_KEY) == S2N_SUCCESS) {
     154                 :         52 :         *type_hint = EVP_PKEY_RSA;
     155                 :         52 :         return S2N_SUCCESS;
     156                 :         52 :     }
     157                 :            : 
     158         [ -  + ]:        501 :     POSIX_GUARD(s2n_stuffer_reread(pem));
     159         [ -  + ]:        501 :     POSIX_GUARD(s2n_stuffer_reread(asn1));
     160                 :            : 
     161                 :            :     /* By default, OpenSSL tools always generate both "EC PARAMETERS" and "EC PRIVATE
     162                 :            :      * KEY" PEM objects in the keyfile. Skip the first "EC PARAMETERS" object so that we're
     163                 :            :      * compatible with OpenSSL's default output, and since "EC PARAMETERS" is
     164                 :            :      * only needed for non-standard curves that aren't currently supported.
     165                 :            :      */
     166         [ +  + ]:        501 :     if (s2n_stuffer_data_from_pem(pem, asn1, S2N_PEM_EC_PARAMETERS) != S2N_SUCCESS) {
     167         [ -  + ]:        350 :         POSIX_GUARD(s2n_stuffer_reread(pem));
     168                 :        350 :     }
     169         [ -  + ]:        501 :     POSIX_GUARD(s2n_stuffer_wipe(asn1));
     170                 :            : 
     171         [ +  + ]:        501 :     if (s2n_stuffer_data_from_pem(pem, asn1, S2N_PEM_PKCS1_EC_PRIVATE_KEY) == S2N_SUCCESS) {
     172                 :        152 :         *type_hint = EVP_PKEY_EC;
     173                 :        152 :         return S2N_SUCCESS;
     174                 :        152 :     }
     175                 :            : 
     176                 :            :     /* If it does not match either format, try PKCS#8 */
     177         [ -  + ]:        349 :     POSIX_GUARD(s2n_stuffer_reread(pem));
     178         [ -  + ]:        349 :     POSIX_GUARD(s2n_stuffer_reread(asn1));
     179         [ +  + ]:        349 :     if (s2n_stuffer_data_from_pem(pem, asn1, S2N_PEM_PKCS8_PRIVATE_KEY) == S2N_SUCCESS) {
     180                 :            :         /* This is the most generic format, and could represent keys other than RSA.
     181                 :            :          * We set RSA only as a default type hint.
     182                 :            :          */
     183                 :        347 :         *type_hint = EVP_PKEY_RSA;
     184                 :        347 :         return S2N_SUCCESS;
     185                 :        347 :     }
     186                 :            : 
     187         [ +  - ]:          2 :     POSIX_BAIL(S2N_ERR_INVALID_PEM);
     188                 :          2 : }
     189                 :            : 
     190                 :            : /*
     191                 :            :  * We identify strings containing a PEM-encapsulated block by searching for the
     192                 :            :  * PEM delimiter chars.
     193                 :            :  *
     194                 :            :  * Although it isn't checked here, the delimiter should also imply the existence
     195                 :            :  * of the BEGIN keyword. This ensures that we don't ignore unexpected keywords
     196                 :            :  * like "END CERTIFICATE" instead of "BEGIN CERTIFICATE" or malformed keywords
     197                 :            :  * like "BEGAN CERTIFICATE" instead of "BEGIN CERTIFICATE".
     198                 :            :  */
     199                 :            : bool s2n_stuffer_has_pem_encapsulated_block(struct s2n_stuffer *pem)
     200                 :       2209 : {
     201                 :            :     /* Operate on a copy of pem to avoid modifying pem */
     202                 :       2209 :     struct s2n_stuffer pem_copy = *pem;
     203                 :       2209 :     return s2n_result_is_ok(s2n_stuffer_pem_read_delimiter_chars(&pem_copy));
     204                 :       2209 : }
     205                 :            : 
     206                 :            : int s2n_stuffer_certificate_from_pem(struct s2n_stuffer *pem, struct s2n_stuffer *asn1)
     207                 :       1824 : {
     208                 :       1824 :     return s2n_stuffer_data_from_pem(pem, asn1, S2N_PEM_CERTIFICATE);
     209                 :       1824 : }
     210                 :            : 
     211                 :            : int s2n_stuffer_crl_from_pem(struct s2n_stuffer *pem, struct s2n_stuffer *asn1)
     212                 :         10 : {
     213                 :         10 :     return s2n_stuffer_data_from_pem(pem, asn1, S2N_PEM_CRL);
     214                 :         10 : }
     215                 :            : 
     216                 :            : int s2n_stuffer_dhparams_from_pem(struct s2n_stuffer *pem, struct s2n_stuffer *pkcs3)
     217                 :         83 : {
     218                 :         83 :     return s2n_stuffer_data_from_pem(pem, pkcs3, S2N_PEM_DH_PARAMETERS);
     219                 :         83 : }

Generated by: LCOV version 1.14