/* SPDX-License-Identifier: GPL-2.0-or-later * * Copyright (C) 2024 Herbert Valerio Riedel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef HS_AES_GCM_H #define HS_AES_GCM_H #include #include #include #include /* return codes */ #define HS_AES_GCM_OK 0 #define HS_AES_GCM_AUTHFAIL 1 #define HS_AES_GCM_ERR (-1) #define HS_AES_GCM_INVAL (-2) typedef enum { HS_AES_GCM_ENC = 1, HS_AES_GCM_DEC = 2 } hs_aes_gcm_op_t; static inline int hs_aes_gcm_ctx_init(EVP_CIPHER_CTX **pctx, const hs_aes_gcm_op_t op, const uint8_t key[], size_t keysize, const uint8_t iv[], size_t ivsize) { if (!(op == HS_AES_GCM_DEC || op == HS_AES_GCM_ENC)) return HS_AES_GCM_INVAL; if (!key || (!iv || ivsize<1)) return HS_AES_GCM_INVAL; const EVP_CIPHER *cipher = NULL; switch(keysize) { case 16: cipher = EVP_aes_128_gcm(); break; case 24: cipher = EVP_aes_192_gcm(); break; case 32: cipher = EVP_aes_256_gcm(); break; default: return HS_AES_GCM_INVAL; } int ret = HS_AES_GCM_ERR; EVP_CIPHER_CTX *ctx = *pctx = EVP_CIPHER_CTX_new(); if (!ctx) goto done; switch(op) { case HS_AES_GCM_ENC: if (ivsize == 12) { if (1 != EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv)) goto done; } else { if (1 != EVP_EncryptInit_ex(ctx, cipher, NULL, key, NULL)) goto done; if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, (int)ivsize, NULL)) goto done; if (1 != EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, iv)) goto done; } break; case HS_AES_GCM_DEC: if (ivsize == 12) { if (1 != EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)) goto done; } else { if (1 != EVP_DecryptInit_ex(ctx, cipher, NULL, key, NULL)) goto done; if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, (int)ivsize, NULL)) goto done; if (1 != EVP_DecryptInit_ex(ctx, NULL, NULL, NULL, iv)) goto done; } break; default: /* impossible */ ret = HS_AES_GCM_INVAL; goto done; } return HS_AES_GCM_OK; done: if (ctx) { EVP_CIPHER_CTX_free(ctx); *pctx = NULL; } if (ret != HS_AES_GCM_OK) ERR_clear_error(); return ret; } static inline int hs_aes_gcm_check_params(const uint8_t aad[], size_t aadsize, const uint8_t tag[], size_t tagsize, const uint8_t ct[], const uint8_t pt[], size_t msgsize) { if (!aad && aadsize>0) return HS_AES_GCM_INVAL; if ((!pt || !ct) && msgsize>0) return HS_AES_GCM_INVAL; if (!tag || !(tagsize == 4 || tagsize == 8 || (tagsize >= 12 && tagsize <= 16))) return HS_AES_GCM_INVAL; return HS_AES_GCM_OK; } /* set dst = NULL for AAD */ static inline int hs_aes_gcm_ctx_update(EVP_CIPHER_CTX *ctx, const hs_aes_gcm_op_t op, const uint8_t src[], uint8_t dst[], const size_t msgsize) { if (!(op == HS_AES_GCM_DEC || op == HS_AES_GCM_ENC)) return HS_AES_GCM_INVAL; const size_t blocksize = 1024*1024; /* guaranteed to fit into an 'int' */ for(size_t ofs = 0; ofs < msgsize; ofs += blocksize) { const size_t n = (ofs + blocksize <= msgsize) ? blocksize : msgsize - ofs; int written = 0; int rc = 0; switch(op) { case HS_AES_GCM_ENC: rc = EVP_EncryptUpdate(ctx, dst ? (dst+ofs) : dst, &written, src+ofs, (int)n); break; case HS_AES_GCM_DEC: rc = EVP_DecryptUpdate(ctx, dst ? (dst+ofs) : dst, &written, src+ofs, (int)n); break; } if (1 != rc) return HS_AES_GCM_ERR; if (n != written) return HS_AES_GCM_ERR; } /* for */ return HS_AES_GCM_OK; } static inline int hs_aes_gcm_ctx_finalize_enc(EVP_CIPHER_CTX *ctx, uint8_t tag[], const size_t tagsize) { int written = 0; if (1 != EVP_EncryptFinal_ex(ctx, NULL, &written)) return HS_AES_GCM_ERR; if (written) return HS_AES_GCM_ERR; if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, (int)tagsize, (void*)tag)) return HS_AES_GCM_ERR; return HS_AES_GCM_OK; } static inline int hs_aes_gcm_ctx_finalize_dec(EVP_CIPHER_CTX *ctx, const uint8_t tag[], const size_t tagsize) { if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, (int)tagsize, (void*)tag)) return HS_AES_GCM_ERR; int written = 0; int rc = EVP_DecryptFinal_ex(ctx, NULL, &written); if (written) return HS_AES_GCM_ERR; switch(rc) { case 1: return HS_AES_GCM_OK; case 0: /* the OpenSSL documentation isn't clear about how to differentiate between auth failures and other internal errors, so we have to trust that when EVP_DecryptFinal_ex returns with 0 it means auth failure */ return HS_AES_GCM_AUTHFAIL; default: return HS_AES_GCM_ERR; } } /*****************************************************************************/ static inline int hs_aes_gcm_enc(const uint8_t key[], const size_t keysize, const uint8_t iv[], const size_t ivsize, uint8_t tag[], const size_t tagsize, const uint8_t aad[], const size_t aadsize, const uint8_t pt[], uint8_t ct[], const size_t msgsize) { EVP_CIPHER_CTX *ctx = NULL; int ret = hs_aes_gcm_check_params(aad, aadsize, tag, tagsize, ct, pt, msgsize); if (HS_AES_GCM_OK != ret) return ret; ret = hs_aes_gcm_ctx_init(&ctx, HS_AES_GCM_ENC, key, keysize, iv, ivsize); if (HS_AES_GCM_OK != ret) return ret; ret = hs_aes_gcm_ctx_update(ctx, HS_AES_GCM_ENC, aad, NULL, aadsize); if (HS_AES_GCM_OK != ret) goto done; ret = hs_aes_gcm_ctx_update(ctx, HS_AES_GCM_ENC, pt, ct, msgsize); if (HS_AES_GCM_OK != ret) goto done; hs_aes_gcm_ctx_finalize_enc(ctx, tag, tagsize); done: if (ctx) EVP_CIPHER_CTX_free(ctx); if (ret != HS_AES_GCM_OK) ERR_clear_error(); return ret; } static inline int hs_aes_gcm_dec(const uint8_t key[], const size_t keysize, const uint8_t iv[], const size_t ivsize, const uint8_t tag[], const size_t tagsize, const uint8_t aad[], const size_t aadsize, const uint8_t ct[], uint8_t pt[], const size_t msgsize) { EVP_CIPHER_CTX *ctx = NULL; int ret = hs_aes_gcm_check_params(aad, aadsize, tag, tagsize, ct, pt, msgsize); if (HS_AES_GCM_OK != ret) return ret; ret = hs_aes_gcm_ctx_init(&ctx, HS_AES_GCM_DEC, key, keysize, iv, ivsize); if (HS_AES_GCM_OK != ret) return ret; ret = hs_aes_gcm_ctx_update(ctx, HS_AES_GCM_DEC, aad, NULL, aadsize); if (HS_AES_GCM_OK != ret) goto done; ret = hs_aes_gcm_ctx_update(ctx, HS_AES_GCM_DEC, ct, pt, msgsize); if (HS_AES_GCM_OK != ret) goto done; ret = hs_aes_gcm_ctx_finalize_dec(ctx, tag, tagsize); done: if (ctx) EVP_CIPHER_CTX_free(ctx); if (ret != HS_AES_GCM_OK) ERR_clear_error(); return ret; } #endif