Commit b0bda138a6 for openssl.org

commit b0bda138a64086485da93e1dc7fa90fd866e62bd
Author: Simo Sorce <simo@redhat.com>
Date:   Thu Jun 11 19:02:27 2026 -0400

    Consolidate s390x AES hardware implementations

    Move the s390x hardware-accelerated AES mode implementations (GCM, CCM, and
    XTS) from their respective files into a single `cipher_aes_hw_s390x.c` file.
    Centralizing the platform-specific logic simplifies the code structure and
    improves maintainability.

    Signed-off-by: Simo Sorce <simo@redhat.com>

    Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
    Reviewed-by: Norbert Pocs <norbertp@openssl.org>
    Reviewed-by: Shane Lontis <shane.lontis@oracle.com>
    MergeDate: Sat Jun 27 09:05:58 2026
    (Merged from https://github.com/openssl/openssl/pull/31472)

diff --git a/providers/implementations/ciphers/build.info b/providers/implementations/ciphers/build.info
index 70e41b3e34..f82cae6762 100644
--- a/providers/implementations/ciphers/build.info
+++ b/providers/implementations/ciphers/build.info
@@ -109,9 +109,9 @@ SOURCE[$AES_GOAL]=\
         cipher_aes_xts.c cipher_aes_xts_hw.c \
         cipher_aes_gcm.c cipher_aes_gcm_hw.c \
         cipher_aes_gcm_hw_ppc.c \
-        cipher_aes_gcm_hw_s390x.c cipher_aes_gcm_hw_t4.c \
+        cipher_aes_gcm_hw_t4.c \
         cipher_aes_ccm.c cipher_aes_ccm_hw.c \
-        cipher_aes_ccm_hw_s390x.c cipher_aes_ccm_hw_t4.c \
+        cipher_aes_ccm_hw_t4.c \
         cipher_aes_wrp.c \
         cipher_aes_cbc_hmac_sha.c \
         cipher_aes_cbc_hmac_sha256_hw.c cipher_aes_cbc_hmac_sha1_hw.c \
diff --git a/providers/implementations/ciphers/cipher_aes_ccm_hw_s390x.c b/providers/implementations/ciphers/cipher_aes_ccm_hw_s390x.c
deleted file mode 100644
index d79e2766bc..0000000000
--- a/providers/implementations/ciphers/cipher_aes_ccm_hw_s390x.c
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Copyright 2001-2020 The OpenSSL Project Authors. All Rights Reserved.
- *
- * Licensed under the Apache License 2.0 (the "License").  You may not use
- * this file except in compliance with the License.  You can obtain a copy
- * in the file LICENSE in the source distribution or at
- * https://www.openssl.org/source/license.html
- */
-
-/*-
- * S390X support for AES CCM.
- * This file is used by cipher_aes_ccm_hw.c
- */
-
-#include "internal/deprecated.h"
-#include "cipher_aes_ccm.h"
-
-#if defined(S390X_aes_128_CAPABLE)
-
-#define S390X_CCM_AAD_FLAG 0x40
-
-static int s390x_aes_ccm_initkey(PROV_CCM_CTX *ctx,
-    const unsigned char *key, size_t keylen)
-{
-    PROV_AES_CCM_CTX *sctx = (PROV_AES_CCM_CTX *)ctx;
-
-    sctx->ccm.s390x.fc = S390X_AES_FC(keylen);
-    memcpy(&sctx->ccm.s390x.kmac.k, key, keylen);
-    /* Store encoded m and l. */
-    sctx->ccm.s390x.nonce.b[0] = ((ctx->l - 1) & 0x7)
-        | (((ctx->m - 2) >> 1) & 0x7) << 3;
-    memset(sctx->ccm.s390x.nonce.b + 1, 0, sizeof(sctx->ccm.s390x.nonce.b));
-    sctx->ccm.s390x.blocks = 0;
-    ctx->key_set = 1;
-    return 1;
-}
-
-static int s390x_aes_ccm_setiv(PROV_CCM_CTX *ctx,
-    const unsigned char *nonce, size_t noncelen,
-    size_t mlen)
-{
-    PROV_AES_CCM_CTX *sctx = (PROV_AES_CCM_CTX *)ctx;
-
-    sctx->ccm.s390x.nonce.b[0] &= ~S390X_CCM_AAD_FLAG;
-    sctx->ccm.s390x.nonce.g[1] = mlen;
-    memcpy(sctx->ccm.s390x.nonce.b + 1, nonce, 15 - ctx->l);
-    return 1;
-}
-
-/*-
- * Process additional authenticated data. Code is big-endian.
- */
-static int s390x_aes_ccm_setaad(PROV_CCM_CTX *ctx,
-    const unsigned char *aad, size_t alen)
-{
-    PROV_AES_CCM_CTX *sctx = (PROV_AES_CCM_CTX *)ctx;
-    unsigned char *ptr;
-    int i, rem;
-
-    if (!alen)
-        return 1;
-
-    sctx->ccm.s390x.nonce.b[0] |= S390X_CCM_AAD_FLAG;
-
-    /* Suppress 'type-punned pointer dereference' warning. */
-    ptr = sctx->ccm.s390x.buf.b;
-
-    if (alen < ((1 << 16) - (1 << 8))) {
-        *(uint16_t *)ptr = alen;
-        i = 2;
-    } else if (sizeof(alen) == 8
-        && alen >= (size_t)1 << (32 % (sizeof(alen) * 8))) {
-        *(uint16_t *)ptr = 0xffff;
-        *(uint64_t *)(ptr + 2) = alen;
-        i = 10;
-    } else {
-        *(uint16_t *)ptr = 0xfffe;
-        *(uint32_t *)(ptr + 2) = alen;
-        i = 6;
-    }
-
-    while (i < 16 && alen) {
-        sctx->ccm.s390x.buf.b[i] = *aad;
-        ++aad;
-        --alen;
-        ++i;
-    }
-    while (i < 16) {
-        sctx->ccm.s390x.buf.b[i] = 0;
-        ++i;
-    }
-
-    sctx->ccm.s390x.kmac.icv.g[0] = 0;
-    sctx->ccm.s390x.kmac.icv.g[1] = 0;
-    s390x_kmac(sctx->ccm.s390x.nonce.b, 32, sctx->ccm.s390x.fc,
-        &sctx->ccm.s390x.kmac);
-    sctx->ccm.s390x.blocks += 2;
-
-    rem = alen & 0xf;
-    alen &= ~(size_t)0xf;
-    if (alen) {
-        s390x_kmac(aad, alen, sctx->ccm.s390x.fc, &sctx->ccm.s390x.kmac);
-        sctx->ccm.s390x.blocks += alen >> 4;
-        aad += alen;
-    }
-    if (rem) {
-        for (i = 0; i < rem; i++)
-            sctx->ccm.s390x.kmac.icv.b[i] ^= aad[i];
-
-        s390x_km(sctx->ccm.s390x.kmac.icv.b, 16,
-            sctx->ccm.s390x.kmac.icv.b, sctx->ccm.s390x.fc,
-            sctx->ccm.s390x.kmac.k);
-        sctx->ccm.s390x.blocks++;
-    }
-    return 1;
-}
-
-/*-
- * En/de-crypt plain/cipher-text. Compute tag from plaintext. Returns 1 for
- * success.
- */
-static int s390x_aes_ccm_auth_encdec(PROV_CCM_CTX *ctx,
-    const unsigned char *in,
-    unsigned char *out, size_t len, int enc)
-{
-    PROV_AES_CCM_CTX *sctx = (PROV_AES_CCM_CTX *)ctx;
-    size_t n, rem;
-    unsigned int i, l, num;
-    unsigned char flags;
-
-    flags = sctx->ccm.s390x.nonce.b[0];
-    if (!(flags & S390X_CCM_AAD_FLAG)) {
-        s390x_km(sctx->ccm.s390x.nonce.b, 16, sctx->ccm.s390x.kmac.icv.b,
-            sctx->ccm.s390x.fc, sctx->ccm.s390x.kmac.k);
-        sctx->ccm.s390x.blocks++;
-    }
-    l = flags & 0x7;
-    sctx->ccm.s390x.nonce.b[0] = l;
-
-    /*-
-     * Reconstruct length from encoded length field
-     * and initialize it with counter value.
-     */
-    n = 0;
-    for (i = 15 - l; i < 15; i++) {
-        n |= sctx->ccm.s390x.nonce.b[i];
-        sctx->ccm.s390x.nonce.b[i] = 0;
-        n <<= 8;
-    }
-    n |= sctx->ccm.s390x.nonce.b[15];
-    sctx->ccm.s390x.nonce.b[15] = 1;
-
-    if (n != len)
-        return 0; /* length mismatch */
-
-    if (enc) {
-        /* Two operations per block plus one for tag encryption */
-        sctx->ccm.s390x.blocks += (((len + 15) >> 4) << 1) + 1;
-        if (sctx->ccm.s390x.blocks > (1ULL << 61))
-            return 0; /* too much data */
-    }
-
-    num = 0;
-    rem = len & 0xf;
-    len &= ~(size_t)0xf;
-
-    if (enc) {
-        /* mac-then-encrypt */
-        if (len)
-            s390x_kmac(in, len, sctx->ccm.s390x.fc, &sctx->ccm.s390x.kmac);
-        if (rem) {
-            for (i = 0; i < rem; i++)
-                sctx->ccm.s390x.kmac.icv.b[i] ^= in[len + i];
-
-            s390x_km(sctx->ccm.s390x.kmac.icv.b, 16,
-                sctx->ccm.s390x.kmac.icv.b,
-                sctx->ccm.s390x.fc, sctx->ccm.s390x.kmac.k);
-        }
-
-        CRYPTO_ctr128_encrypt_ctr32(in, out, len + rem, &sctx->ccm.ks.ks,
-            sctx->ccm.s390x.nonce.b, sctx->ccm.s390x.buf.b,
-            &num, (ctr128_f)AES_ctr32_encrypt);
-    } else {
-        /* decrypt-then-mac */
-        CRYPTO_ctr128_encrypt_ctr32(in, out, len + rem, &sctx->ccm.ks.ks,
-            sctx->ccm.s390x.nonce.b, sctx->ccm.s390x.buf.b,
-            &num, (ctr128_f)AES_ctr32_encrypt);
-
-        if (len)
-            s390x_kmac(out, len, sctx->ccm.s390x.fc, &sctx->ccm.s390x.kmac);
-        if (rem) {
-            for (i = 0; i < rem; i++)
-                sctx->ccm.s390x.kmac.icv.b[i] ^= out[len + i];
-
-            s390x_km(sctx->ccm.s390x.kmac.icv.b, 16,
-                sctx->ccm.s390x.kmac.icv.b,
-                sctx->ccm.s390x.fc, sctx->ccm.s390x.kmac.k);
-        }
-    }
-    /* encrypt tag */
-    for (i = 15 - l; i < 16; i++)
-        sctx->ccm.s390x.nonce.b[i] = 0;
-
-    s390x_km(sctx->ccm.s390x.nonce.b, 16, sctx->ccm.s390x.buf.b,
-        sctx->ccm.s390x.fc, sctx->ccm.s390x.kmac.k);
-    sctx->ccm.s390x.kmac.icv.g[0] ^= sctx->ccm.s390x.buf.g[0];
-    sctx->ccm.s390x.kmac.icv.g[1] ^= sctx->ccm.s390x.buf.g[1];
-
-    sctx->ccm.s390x.nonce.b[0] = flags; /* restore flags field */
-    return 1;
-}
-
-static int s390x_aes_ccm_gettag(PROV_CCM_CTX *ctx,
-    unsigned char *tag, size_t tlen)
-{
-    PROV_AES_CCM_CTX *sctx = (PROV_AES_CCM_CTX *)ctx;
-
-    if (tlen > ctx->m)
-        return 0;
-    memcpy(tag, sctx->ccm.s390x.kmac.icv.b, tlen);
-    return 1;
-}
-
-static int s390x_aes_ccm_auth_encrypt(PROV_CCM_CTX *ctx,
-    const unsigned char *in,
-    unsigned char *out, size_t len,
-    unsigned char *tag, size_t taglen)
-{
-    int rv;
-
-    rv = s390x_aes_ccm_auth_encdec(ctx, in, out, len, 1);
-    if (rv && tag != NULL)
-        rv = s390x_aes_ccm_gettag(ctx, tag, taglen);
-    return rv;
-}
-
-static int s390x_aes_ccm_auth_decrypt(PROV_CCM_CTX *ctx,
-    const unsigned char *in,
-    unsigned char *out, size_t len,
-    unsigned char *expected_tag,
-    size_t taglen)
-{
-    int rv = 0;
-    PROV_AES_CCM_CTX *sctx = (PROV_AES_CCM_CTX *)ctx;
-
-    rv = s390x_aes_ccm_auth_encdec(ctx, in, out, len, 0);
-    if (rv) {
-        if (CRYPTO_memcmp(sctx->ccm.s390x.kmac.icv.b, expected_tag, ctx->m) != 0)
-            rv = 0;
-    }
-    if (rv == 0)
-        OPENSSL_cleanse(out, len);
-    return rv;
-}
-
-static const PROV_CCM_HW s390x_aes_ccm = {
-    s390x_aes_ccm_initkey,
-    s390x_aes_ccm_setiv,
-    s390x_aes_ccm_setaad,
-    s390x_aes_ccm_auth_encrypt,
-    s390x_aes_ccm_auth_decrypt,
-    s390x_aes_ccm_gettag
-};
-
-const PROV_CCM_HW *ossl_prov_aes_hw_ccm_s390x(size_t keybits)
-{
-    if ((keybits == 128 && S390X_aes_128_ccm_CAPABLE)
-        || (keybits == 192 && S390X_aes_192_ccm_CAPABLE)
-        || (keybits == 256 && S390X_aes_256_ccm_CAPABLE))
-        return &s390x_aes_ccm;
-    return NULL;
-}
-#endif
diff --git a/providers/implementations/ciphers/cipher_aes_gcm_hw_s390x.c b/providers/implementations/ciphers/cipher_aes_gcm_hw_s390x.c
deleted file mode 100644
index 4c154744e0..0000000000
--- a/providers/implementations/ciphers/cipher_aes_gcm_hw_s390x.c
+++ /dev/null
@@ -1,317 +0,0 @@
-/*
- * Copyright 2001-2021 The OpenSSL Project Authors. All Rights Reserved.
- *
- * Licensed under the Apache License 2.0 (the "License").  You may not use
- * this file except in compliance with the License.  You can obtain a copy
- * in the file LICENSE in the source distribution or at
- * https://www.openssl.org/source/license.html
- */
-
-/*-
- * IBM S390X support for AES GCM.
- * This file is used by cipher_aes_gcm_hw.c
- */
-#include "internal/deprecated.h"
-#include "cipher_aes_gcm.h"
-
-#if defined(S390X_aes_128_CAPABLE)
-
-/* iv + padding length for iv lengths != 12 */
-#define S390X_gcm_ivpadlen(i) ((((i) + 15) >> 4 << 4) + 16)
-
-/* Additional flag or'ed to fc for decryption */
-#define S390X_gcm_decrypt_flag(ctx) (((ctx)->enc) ? 0 : S390X_DECRYPT)
-
-#define S390X_gcm_fc(A, C) ((A)->plat.s390x.fc | (A)->plat.s390x.hsflag | S390X_gcm_decrypt_flag((C)))
-
-static int s390x_aes_gcm_initkey(PROV_GCM_CTX *ctx,
-    const unsigned char *key, size_t keylen)
-{
-    PROV_AES_GCM_CTX *actx = (PROV_AES_GCM_CTX *)ctx;
-
-    ctx->key_set = 1;
-    memcpy(&actx->plat.s390x.param.kma.k, key, keylen);
-    actx->plat.s390x.fc = S390X_AES_FC(keylen);
-    return 1;
-}
-
-static int s390x_aes_gcm_setiv(PROV_GCM_CTX *ctx, const unsigned char *iv,
-    size_t ivlen)
-{
-    PROV_AES_GCM_CTX *actx = (PROV_AES_GCM_CTX *)ctx;
-    S390X_KMA_PARAMS *kma = &actx->plat.s390x.param.kma;
-
-    kma->t.g[0] = 0;
-    kma->t.g[1] = 0;
-    kma->tpcl = 0;
-    kma->taadl = 0;
-    actx->plat.s390x.mreslen = 0;
-    actx->plat.s390x.areslen = 0;
-    actx->plat.s390x.kreslen = 0;
-
-    if (ivlen == GCM_IV_DEFAULT_SIZE) {
-        memcpy(&kma->j0, iv, ivlen);
-        kma->j0.w[3] = 1;
-        kma->cv.w = 1;
-        actx->plat.s390x.hsflag = 0;
-    } else {
-        unsigned long long ivbits = ivlen << 3;
-        size_t len = S390X_gcm_ivpadlen(ivlen);
-        unsigned char iv_zero_pad[S390X_gcm_ivpadlen(GCM_IV_MAX_SIZE)];
-        /*
-         * The IV length needs to be zero padded to be a multiple of 16 bytes
-         * followed by 8 bytes of zeros and 8 bytes for the IV length.
-         * The GHASH of this value can then be calculated.
-         */
-        memcpy(iv_zero_pad, iv, ivlen);
-        memset(iv_zero_pad + ivlen, 0, len - ivlen);
-        memcpy(iv_zero_pad + len - sizeof(ivbits), &ivbits, sizeof(ivbits));
-        /*
-         * Calculate the ghash of the iv - the result is stored into the tag
-         * param.
-         */
-        s390x_kma(iv_zero_pad, len, NULL, 0, NULL, actx->plat.s390x.fc, kma);
-        actx->plat.s390x.hsflag = S390X_KMA_HS; /* The hash subkey is set */
-
-        /* Copy the 128 bit GHASH result into J0 and clear the tag */
-        kma->j0.g[0] = kma->t.g[0];
-        kma->j0.g[1] = kma->t.g[1];
-        kma->t.g[0] = 0;
-        kma->t.g[1] = 0;
-        /* Set the 32 bit counter */
-        kma->cv.w = kma->j0.w[3];
-    }
-    return 1;
-}
-
-static int s390x_aes_gcm_cipher_final(PROV_GCM_CTX *ctx, unsigned char *tag)
-{
-    PROV_AES_GCM_CTX *actx = (PROV_AES_GCM_CTX *)ctx;
-    S390X_KMA_PARAMS *kma = &actx->plat.s390x.param.kma;
-    unsigned char out[AES_BLOCK_SIZE];
-    unsigned int fc;
-    int rc;
-
-    kma->taadl <<= 3;
-    kma->tpcl <<= 3;
-    fc = S390X_gcm_fc(actx, ctx) | S390X_KMA_LAAD | S390X_KMA_LPC;
-    s390x_kma(actx->plat.s390x.ares, actx->plat.s390x.areslen,
-        actx->plat.s390x.mres, actx->plat.s390x.mreslen, out,
-        fc, kma);
-
-    /* gctx->mres already returned to the caller */
-    OPENSSL_cleanse(out, actx->plat.s390x.mreslen);
-
-    if (ctx->enc) {
-        ctx->taglen = GCM_TAG_MAX_SIZE;
-        memcpy(tag, kma->t.b, ctx->taglen);
-        rc = 1;
-    } else {
-        rc = (CRYPTO_memcmp(tag, kma->t.b, ctx->taglen) == 0);
-    }
-    return rc;
-}
-
-static int s390x_aes_gcm_one_shot(PROV_GCM_CTX *ctx,
-    unsigned char *aad, size_t aad_len,
-    const unsigned char *in, size_t in_len,
-    unsigned char *out,
-    unsigned char *tag, size_t taglen)
-{
-    PROV_AES_GCM_CTX *actx = (PROV_AES_GCM_CTX *)ctx;
-    S390X_KMA_PARAMS *kma = &actx->plat.s390x.param.kma;
-    unsigned int fc;
-    int rc;
-
-    kma->taadl = aad_len << 3;
-    kma->tpcl = in_len << 3;
-    fc = S390X_gcm_fc(actx, ctx) | S390X_KMA_LAAD | S390X_KMA_LPC;
-    s390x_kma(aad, aad_len, in, in_len, out, fc, kma);
-
-    if (ctx->enc) {
-        memcpy(tag, kma->t.b, taglen);
-        rc = 1;
-    } else {
-        rc = (CRYPTO_memcmp(tag, kma->t.b, taglen) == 0);
-    }
-    return rc;
-}
-
-/*
- * Process additional authenticated data. Returns 1 on success. Code is
- * big-endian.
- */
-static int s390x_aes_gcm_aad_update(PROV_GCM_CTX *ctx,
-    const unsigned char *aad, size_t len)
-{
-    PROV_AES_GCM_CTX *actx = (PROV_AES_GCM_CTX *)ctx;
-    S390X_KMA_PARAMS *kma = &actx->plat.s390x.param.kma;
-    unsigned long long alen;
-    unsigned int fc;
-    int n, rem;
-
-    /* If already processed pt/ct then error */
-    if (kma->tpcl != 0)
-        return 0;
-
-    /* update the total aad length */
-    alen = kma->taadl + len;
-    if (alen > (U64(1) << 61) || (sizeof(len) == 8 && alen < len))
-        return 0;
-    kma->taadl = alen;
-
-    /* check if there is any existing aad data from a previous add */
-    n = actx->plat.s390x.areslen;
-    if (n) {
-        /* add additional data to a buffer until it has 16 bytes */
-        while (n && len) {
-            actx->plat.s390x.ares[n] = *aad;
-            ++aad;
-            --len;
-            n = (n + 1) & 0xf;
-        }
-        /* ctx->ares contains a complete block if offset has wrapped around */
-        if (!n) {
-            fc = S390X_gcm_fc(actx, ctx);
-            s390x_kma(actx->plat.s390x.ares, 16, NULL, 0, NULL, fc, kma);
-            actx->plat.s390x.hsflag = S390X_KMA_HS;
-        }
-        actx->plat.s390x.areslen = n;
-    }
-
-    /* If there are leftover bytes (< 128 bits) save them for next time */
-    rem = len & 0xf;
-    /* Add any remaining 16 byte blocks (128 bit each) */
-    len &= ~(size_t)0xf;
-    if (len) {
-        fc = S390X_gcm_fc(actx, ctx);
-        s390x_kma(aad, len, NULL, 0, NULL, fc, kma);
-        actx->plat.s390x.hsflag = S390X_KMA_HS;
-        aad += len;
-    }
-
-    if (rem) {
-        actx->plat.s390x.areslen = rem;
-
-        do {
-            --rem;
-            actx->plat.s390x.ares[rem] = aad[rem];
-        } while (rem);
-    }
-    return 1;
-}
-
-/*-
- * En/de-crypt plain/cipher-text and authenticate ciphertext. Returns 1 for
- * success. Code is big-endian.
- */
-static int s390x_aes_gcm_cipher_update(PROV_GCM_CTX *ctx,
-    const unsigned char *in, size_t len,
-    unsigned char *out)
-{
-    PROV_AES_GCM_CTX *actx = (PROV_AES_GCM_CTX *)ctx;
-    S390X_KMA_PARAMS *kma = &actx->plat.s390x.param.kma;
-    const unsigned char *inptr;
-    unsigned long long mlen;
-    unsigned int fc;
-    union {
-        unsigned int w[4];
-        unsigned char b[16];
-    } buf;
-    size_t inlen;
-    int n, rem, i;
-
-    mlen = kma->tpcl + len;
-    if (mlen > ((U64(1) << 36) - 32) || (sizeof(len) == 8 && mlen < len))
-        return 0;
-    kma->tpcl = mlen;
-
-    fc = S390X_gcm_fc(actx, ctx) | S390X_KMA_LAAD;
-    n = actx->plat.s390x.mreslen;
-    if (n) {
-        inptr = in;
-        inlen = len;
-        while (n && inlen) {
-            actx->plat.s390x.mres[n] = *inptr;
-            n = (n + 1) & 0xf;
-            ++inptr;
-            --inlen;
-        }
-        /* ctx->mres contains a complete block if offset has wrapped around */
-        if (!n) {
-            s390x_kma(actx->plat.s390x.ares, actx->plat.s390x.areslen,
-                actx->plat.s390x.mres, 16, buf.b, fc, kma);
-            actx->plat.s390x.hsflag = S390X_KMA_HS;
-            fc |= S390X_KMA_HS;
-            actx->plat.s390x.areslen = 0;
-
-            /* previous call already encrypted/decrypted its remainder,
-             * see comment below */
-            n = actx->plat.s390x.mreslen;
-            while (n) {
-                *out = buf.b[n];
-                n = (n + 1) & 0xf;
-                ++out;
-                ++in;
-                --len;
-            }
-            actx->plat.s390x.mreslen = 0;
-        }
-    }
-
-    rem = len & 0xf;
-
-    len &= ~(size_t)0xf;
-    if (len) {
-        s390x_kma(actx->plat.s390x.ares, actx->plat.s390x.areslen, in, len, out,
-            fc, kma);
-        in += len;
-        out += len;
-        actx->plat.s390x.hsflag = S390X_KMA_HS;
-        actx->plat.s390x.areslen = 0;
-    }
-
-    /*-
-     * If there is a remainder, it has to be saved such that it can be
-     * processed by kma later. However, we also have to do the for-now
-     * unauthenticated encryption/decryption part here and now...
-     */
-    if (rem) {
-        if (!actx->plat.s390x.mreslen) {
-            buf.w[0] = kma->j0.w[0];
-            buf.w[1] = kma->j0.w[1];
-            buf.w[2] = kma->j0.w[2];
-            buf.w[3] = kma->cv.w + 1;
-            s390x_km(buf.b, 16, actx->plat.s390x.kres,
-                fc & 0x1f, &kma->k);
-        }
-
-        n = actx->plat.s390x.mreslen;
-        for (i = 0; i < rem; i++) {
-            actx->plat.s390x.mres[n + i] = in[i];
-            out[i] = in[i] ^ actx->plat.s390x.kres[n + i];
-        }
-        actx->plat.s390x.mreslen += rem;
-    }
-    return 1;
-}
-
-static const PROV_GCM_HW s390x_aes_gcm = {
-    s390x_aes_gcm_initkey,
-    s390x_aes_gcm_setiv,
-    s390x_aes_gcm_aad_update,
-    s390x_aes_gcm_cipher_update,
-    s390x_aes_gcm_cipher_final,
-    s390x_aes_gcm_one_shot
-};
-
-const PROV_GCM_HW *ossl_prov_aes_hw_gcm_s390x(size_t keybits)
-{
-    if ((keybits == 128 && S390X_aes_128_gcm_CAPABLE)
-        || (keybits == 192 && S390X_aes_192_gcm_CAPABLE)
-        || (keybits == 256 && S390X_aes_256_gcm_CAPABLE))
-        return &s390x_aes_gcm;
-    return NULL;
-}
-
-#endif
diff --git a/providers/implementations/ciphers/cipher_aes_hw_s390x.c b/providers/implementations/ciphers/cipher_aes_hw_s390x.c
index 30d30d5de7..9c3b2e91aa 100644
--- a/providers/implementations/ciphers/cipher_aes_hw_s390x.c
+++ b/providers/implementations/ciphers/cipher_aes_hw_s390x.c
@@ -1,5 +1,5 @@
 /*
- * Copyright 2001-2025 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 2001-2026 The OpenSSL Project Authors. All Rights Reserved.
  *
  * Licensed under the Apache License 2.0 (the "License").  You may not use
  * this file except in compliance with the License.  You can obtain a copy
@@ -8,17 +8,21 @@
  */

 /*
- * IBM S390X support for AES modes ecb, cbc, ofb, cfb, ctr.
- * This file is used by cipher_aes_hw.c
+ * IBM S390X support for all hardware accelerated AES modes.
  */

 #include "internal/deprecated.h"
+#include <openssl/proverr.h>
 #include "cipher_aes.h"
-#include "arch/s390x_arch.h"
+#include "cipher_aes_gcm.h"
+#include "cipher_aes_ccm.h"
+#include "cipher_aes_xts.h"
 #include <stdio.h>

 #if defined(S390X_aes_128_CAPABLE)

+/* MODES: ecb, cfb, ofb */
+
 static int s390x_aes_ecb_initkey(PROV_CIPHER_CTX *dat,
     const unsigned char *key, size_t keylen)
 {
@@ -237,4 +241,702 @@ const PROV_CIPHER_HW *ossl_prov_cipher_hw_s390x(enum aes_modes mode,
     return NULL;
 }

+/* MODES: GCM */
+
+/* iv + padding length for iv lengths != 12 */
+#define S390X_gcm_ivpadlen(i) ((((i) + 15) >> 4 << 4) + 16)
+
+/* Additional flag or'ed to fc for decryption */
+#define S390X_gcm_decrypt_flag(ctx) (((ctx)->enc) ? 0 : S390X_DECRYPT)
+
+#define S390X_gcm_fc(A, C) ((A)->plat.s390x.fc | (A)->plat.s390x.hsflag | S390X_gcm_decrypt_flag((C)))
+
+static int s390x_aes_gcm_initkey(PROV_GCM_CTX *ctx,
+    const unsigned char *key, size_t keylen)
+{
+    PROV_AES_GCM_CTX *actx = (PROV_AES_GCM_CTX *)ctx;
+
+    ctx->key_set = 1;
+    memcpy(&actx->plat.s390x.param.kma.k, key, keylen);
+    actx->plat.s390x.fc = S390X_AES_FC(keylen);
+    return 1;
+}
+
+static int s390x_aes_gcm_setiv(PROV_GCM_CTX *ctx, const unsigned char *iv,
+    size_t ivlen)
+{
+    PROV_AES_GCM_CTX *actx = (PROV_AES_GCM_CTX *)ctx;
+    S390X_KMA_PARAMS *kma = &actx->plat.s390x.param.kma;
+
+    kma->t.g[0] = 0;
+    kma->t.g[1] = 0;
+    kma->tpcl = 0;
+    kma->taadl = 0;
+    actx->plat.s390x.mreslen = 0;
+    actx->plat.s390x.areslen = 0;
+    actx->plat.s390x.kreslen = 0;
+
+    if (ivlen == GCM_IV_DEFAULT_SIZE) {
+        memcpy(&kma->j0, iv, ivlen);
+        kma->j0.w[3] = 1;
+        kma->cv.w = 1;
+        actx->plat.s390x.hsflag = 0;
+    } else {
+        unsigned long long ivbits = ivlen << 3;
+        size_t len = S390X_gcm_ivpadlen(ivlen);
+        unsigned char iv_zero_pad[S390X_gcm_ivpadlen(GCM_IV_MAX_SIZE)];
+        /*
+         * The IV length needs to be zero padded to be a multiple of 16 bytes
+         * followed by 8 bytes of zeros and 8 bytes for the IV length.
+         * The GHASH of this value can then be calculated.
+         */
+        memcpy(iv_zero_pad, iv, ivlen);
+        memset(iv_zero_pad + ivlen, 0, len - ivlen);
+        memcpy(iv_zero_pad + len - sizeof(ivbits), &ivbits, sizeof(ivbits));
+        /*
+         * Calculate the ghash of the iv - the result is stored into the tag
+         * param.
+         */
+        s390x_kma(iv_zero_pad, len, NULL, 0, NULL, actx->plat.s390x.fc, kma);
+        actx->plat.s390x.hsflag = S390X_KMA_HS; /* The hash subkey is set */
+
+        /* Copy the 128 bit GHASH result into J0 and clear the tag */
+        kma->j0.g[0] = kma->t.g[0];
+        kma->j0.g[1] = kma->t.g[1];
+        kma->t.g[0] = 0;
+        kma->t.g[1] = 0;
+        /* Set the 32 bit counter */
+        kma->cv.w = kma->j0.w[3];
+    }
+    return 1;
+}
+
+static int s390x_aes_gcm_cipher_final(PROV_GCM_CTX *ctx, unsigned char *tag)
+{
+    PROV_AES_GCM_CTX *actx = (PROV_AES_GCM_CTX *)ctx;
+    S390X_KMA_PARAMS *kma = &actx->plat.s390x.param.kma;
+    unsigned char out[AES_BLOCK_SIZE];
+    unsigned int fc;
+    int rc;
+
+    kma->taadl <<= 3;
+    kma->tpcl <<= 3;
+    fc = S390X_gcm_fc(actx, ctx) | S390X_KMA_LAAD | S390X_KMA_LPC;
+    s390x_kma(actx->plat.s390x.ares, actx->plat.s390x.areslen,
+        actx->plat.s390x.mres, actx->plat.s390x.mreslen, out,
+        fc, kma);
+
+    /* gctx->mres already returned to the caller */
+    OPENSSL_cleanse(out, actx->plat.s390x.mreslen);
+
+    if (ctx->enc) {
+        ctx->taglen = GCM_TAG_MAX_SIZE;
+        memcpy(tag, kma->t.b, ctx->taglen);
+        rc = 1;
+    } else {
+        rc = (CRYPTO_memcmp(tag, kma->t.b, ctx->taglen) == 0);
+    }
+    return rc;
+}
+
+static int s390x_aes_gcm_one_shot(PROV_GCM_CTX *ctx,
+    unsigned char *aad, size_t aad_len,
+    const unsigned char *in, size_t in_len,
+    unsigned char *out,
+    unsigned char *tag, size_t taglen)
+{
+    PROV_AES_GCM_CTX *actx = (PROV_AES_GCM_CTX *)ctx;
+    S390X_KMA_PARAMS *kma = &actx->plat.s390x.param.kma;
+    unsigned int fc;
+    int rc;
+
+    kma->taadl = aad_len << 3;
+    kma->tpcl = in_len << 3;
+    fc = S390X_gcm_fc(actx, ctx) | S390X_KMA_LAAD | S390X_KMA_LPC;
+    s390x_kma(aad, aad_len, in, in_len, out, fc, kma);
+
+    if (ctx->enc) {
+        memcpy(tag, kma->t.b, taglen);
+        rc = 1;
+    } else {
+        rc = (CRYPTO_memcmp(tag, kma->t.b, taglen) == 0);
+    }
+    return rc;
+}
+
+/*
+ * Process additional authenticated data. Returns 1 on success. Code is
+ * big-endian.
+ */
+static int s390x_aes_gcm_aad_update(PROV_GCM_CTX *ctx,
+    const unsigned char *aad, size_t len)
+{
+    PROV_AES_GCM_CTX *actx = (PROV_AES_GCM_CTX *)ctx;
+    S390X_KMA_PARAMS *kma = &actx->plat.s390x.param.kma;
+    unsigned long long alen;
+    unsigned int fc;
+    int n, rem;
+
+    /* If already processed pt/ct then error */
+    if (kma->tpcl != 0)
+        return 0;
+
+    /* update the total aad length */
+    alen = kma->taadl + len;
+    if (alen > (U64(1) << 61) || (sizeof(len) == 8 && alen < len))
+        return 0;
+    kma->taadl = alen;
+
+    /* check if there is any existing aad data from a previous add */
+    n = actx->plat.s390x.areslen;
+    if (n) {
+        /* add additional data to a buffer until it has 16 bytes */
+        while (n && len) {
+            actx->plat.s390x.ares[n] = *aad;
+            ++aad;
+            --len;
+            n = (n + 1) & 0xf;
+        }
+        /* ctx->ares contains a complete block if offset has wrapped around */
+        if (!n) {
+            fc = S390X_gcm_fc(actx, ctx);
+            s390x_kma(actx->plat.s390x.ares, 16, NULL, 0, NULL, fc, kma);
+            actx->plat.s390x.hsflag = S390X_KMA_HS;
+        }
+        actx->plat.s390x.areslen = n;
+    }
+
+    /* If there are leftover bytes (< 128 bits) save them for next time */
+    rem = len & 0xf;
+    /* Add any remaining 16 byte blocks (128 bit each) */
+    len &= ~(size_t)0xf;
+    if (len) {
+        fc = S390X_gcm_fc(actx, ctx);
+        s390x_kma(aad, len, NULL, 0, NULL, fc, kma);
+        actx->plat.s390x.hsflag = S390X_KMA_HS;
+        aad += len;
+    }
+
+    if (rem) {
+        actx->plat.s390x.areslen = rem;
+
+        do {
+            --rem;
+            actx->plat.s390x.ares[rem] = aad[rem];
+        } while (rem);
+    }
+    return 1;
+}
+
+/*-
+ * En/de-crypt plain/cipher-text and authenticate ciphertext. Returns 1 for
+ * success. Code is big-endian.
+ */
+static int s390x_aes_gcm_cipher_update(PROV_GCM_CTX *ctx,
+    const unsigned char *in, size_t len,
+    unsigned char *out)
+{
+    PROV_AES_GCM_CTX *actx = (PROV_AES_GCM_CTX *)ctx;
+    S390X_KMA_PARAMS *kma = &actx->plat.s390x.param.kma;
+    const unsigned char *inptr;
+    unsigned long long mlen;
+    unsigned int fc;
+    union {
+        unsigned int w[4];
+        unsigned char b[16];
+    } buf;
+    size_t inlen;
+    int n, rem, i;
+
+    mlen = kma->tpcl + len;
+    if (mlen > ((U64(1) << 36) - 32) || (sizeof(len) == 8 && mlen < len))
+        return 0;
+    kma->tpcl = mlen;
+
+    fc = S390X_gcm_fc(actx, ctx) | S390X_KMA_LAAD;
+    n = actx->plat.s390x.mreslen;
+    if (n) {
+        inptr = in;
+        inlen = len;
+        while (n && inlen) {
+            actx->plat.s390x.mres[n] = *inptr;
+            n = (n + 1) & 0xf;
+            ++inptr;
+            --inlen;
+        }
+        /* ctx->mres contains a complete block if offset has wrapped around */
+        if (!n) {
+            s390x_kma(actx->plat.s390x.ares, actx->plat.s390x.areslen,
+                actx->plat.s390x.mres, 16, buf.b, fc, kma);
+            actx->plat.s390x.hsflag = S390X_KMA_HS;
+            fc |= S390X_KMA_HS;
+            actx->plat.s390x.areslen = 0;
+
+            /* previous call already encrypted/decrypted its remainder,
+             * see comment below */
+            n = actx->plat.s390x.mreslen;
+            while (n) {
+                *out = buf.b[n];
+                n = (n + 1) & 0xf;
+                ++out;
+                ++in;
+                --len;
+            }
+            actx->plat.s390x.mreslen = 0;
+        }
+    }
+
+    rem = len & 0xf;
+
+    len &= ~(size_t)0xf;
+    if (len) {
+        s390x_kma(actx->plat.s390x.ares, actx->plat.s390x.areslen, in, len, out,
+            fc, kma);
+        in += len;
+        out += len;
+        actx->plat.s390x.hsflag = S390X_KMA_HS;
+        actx->plat.s390x.areslen = 0;
+    }
+
+    /*-
+     * If there is a remainder, it has to be saved such that it can be
+     * processed by kma later. However, we also have to do the for-now
+     * unauthenticated encryption/decryption part here and now...
+     */
+    if (rem) {
+        if (!actx->plat.s390x.mreslen) {
+            buf.w[0] = kma->j0.w[0];
+            buf.w[1] = kma->j0.w[1];
+            buf.w[2] = kma->j0.w[2];
+            buf.w[3] = kma->cv.w + 1;
+            s390x_km(buf.b, 16, actx->plat.s390x.kres,
+                fc & 0x1f, &kma->k);
+        }
+
+        n = actx->plat.s390x.mreslen;
+        for (i = 0; i < rem; i++) {
+            actx->plat.s390x.mres[n + i] = in[i];
+            out[i] = in[i] ^ actx->plat.s390x.kres[n + i];
+        }
+        actx->plat.s390x.mreslen += rem;
+    }
+    return 1;
+}
+
+static const PROV_GCM_HW s390x_aes_gcm = {
+    s390x_aes_gcm_initkey,
+    s390x_aes_gcm_setiv,
+    s390x_aes_gcm_aad_update,
+    s390x_aes_gcm_cipher_update,
+    s390x_aes_gcm_cipher_final,
+    s390x_aes_gcm_one_shot
+};
+
+const PROV_GCM_HW *ossl_prov_aes_hw_gcm_s390x(size_t keybits)
+{
+    if ((keybits == 128 && S390X_aes_128_gcm_CAPABLE)
+        || (keybits == 192 && S390X_aes_192_gcm_CAPABLE)
+        || (keybits == 256 && S390X_aes_256_gcm_CAPABLE))
+        return &s390x_aes_gcm;
+    return NULL;
+}
+
+/* MODES: CCM */
+
+#define S390X_CCM_AAD_FLAG 0x40
+
+static int s390x_aes_ccm_initkey(PROV_CCM_CTX *ctx,
+    const unsigned char *key, size_t keylen)
+{
+    PROV_AES_CCM_CTX *sctx = (PROV_AES_CCM_CTX *)ctx;
+
+    sctx->ccm.s390x.fc = S390X_AES_FC(keylen);
+    memcpy(&sctx->ccm.s390x.kmac.k, key, keylen);
+    /* Store encoded m and l. */
+    sctx->ccm.s390x.nonce.b[0] = ((ctx->l - 1) & 0x7)
+        | (((ctx->m - 2) >> 1) & 0x7) << 3;
+    memset(sctx->ccm.s390x.nonce.b + 1, 0, sizeof(sctx->ccm.s390x.nonce.b));
+    sctx->ccm.s390x.blocks = 0;
+    ctx->key_set = 1;
+    return 1;
+}
+
+static int s390x_aes_ccm_setiv(PROV_CCM_CTX *ctx,
+    const unsigned char *nonce, size_t noncelen,
+    size_t mlen)
+{
+    PROV_AES_CCM_CTX *sctx = (PROV_AES_CCM_CTX *)ctx;
+
+    sctx->ccm.s390x.nonce.b[0] &= ~S390X_CCM_AAD_FLAG;
+    sctx->ccm.s390x.nonce.g[1] = mlen;
+    memcpy(sctx->ccm.s390x.nonce.b + 1, nonce, 15 - ctx->l);
+    return 1;
+}
+
+/*-
+ * Process additional authenticated data. Code is big-endian.
+ */
+static int s390x_aes_ccm_setaad(PROV_CCM_CTX *ctx,
+    const unsigned char *aad, size_t alen)
+{
+    PROV_AES_CCM_CTX *sctx = (PROV_AES_CCM_CTX *)ctx;
+    unsigned char *ptr;
+    int i, rem;
+
+    if (!alen)
+        return 1;
+
+    sctx->ccm.s390x.nonce.b[0] |= S390X_CCM_AAD_FLAG;
+
+    /* Suppress 'type-punned pointer dereference' warning. */
+    ptr = sctx->ccm.s390x.buf.b;
+
+    if (alen < ((1 << 16) - (1 << 8))) {
+        *(uint16_t *)ptr = alen;
+        i = 2;
+    } else if (sizeof(alen) == 8
+        && alen >= (size_t)1 << (32 % (sizeof(alen) * 8))) {
+        *(uint16_t *)ptr = 0xffff;
+        *(uint64_t *)(ptr + 2) = alen;
+        i = 10;
+    } else {
+        *(uint16_t *)ptr = 0xfffe;
+        *(uint32_t *)(ptr + 2) = alen;
+        i = 6;
+    }
+
+    while (i < 16 && alen) {
+        sctx->ccm.s390x.buf.b[i] = *aad;
+        ++aad;
+        --alen;
+        ++i;
+    }
+    while (i < 16) {
+        sctx->ccm.s390x.buf.b[i] = 0;
+        ++i;
+    }
+
+    sctx->ccm.s390x.kmac.icv.g[0] = 0;
+    sctx->ccm.s390x.kmac.icv.g[1] = 0;
+    s390x_kmac(sctx->ccm.s390x.nonce.b, 32, sctx->ccm.s390x.fc,
+        &sctx->ccm.s390x.kmac);
+    sctx->ccm.s390x.blocks += 2;
+
+    rem = alen & 0xf;
+    alen &= ~(size_t)0xf;
+    if (alen) {
+        s390x_kmac(aad, alen, sctx->ccm.s390x.fc, &sctx->ccm.s390x.kmac);
+        sctx->ccm.s390x.blocks += alen >> 4;
+        aad += alen;
+    }
+    if (rem) {
+        for (i = 0; i < rem; i++)
+            sctx->ccm.s390x.kmac.icv.b[i] ^= aad[i];
+
+        s390x_km(sctx->ccm.s390x.kmac.icv.b, 16,
+            sctx->ccm.s390x.kmac.icv.b, sctx->ccm.s390x.fc,
+            sctx->ccm.s390x.kmac.k);
+        sctx->ccm.s390x.blocks++;
+    }
+    return 1;
+}
+
+/*-
+ * En/de-crypt plain/cipher-text. Compute tag from plaintext. Returns 1 for
+ * success.
+ */
+static int s390x_aes_ccm_auth_encdec(PROV_CCM_CTX *ctx,
+    const unsigned char *in,
+    unsigned char *out, size_t len, int enc)
+{
+    PROV_AES_CCM_CTX *sctx = (PROV_AES_CCM_CTX *)ctx;
+    size_t n, rem;
+    unsigned int i, l, num;
+    unsigned char flags;
+
+    flags = sctx->ccm.s390x.nonce.b[0];
+    if (!(flags & S390X_CCM_AAD_FLAG)) {
+        s390x_km(sctx->ccm.s390x.nonce.b, 16, sctx->ccm.s390x.kmac.icv.b,
+            sctx->ccm.s390x.fc, sctx->ccm.s390x.kmac.k);
+        sctx->ccm.s390x.blocks++;
+    }
+    l = flags & 0x7;
+    sctx->ccm.s390x.nonce.b[0] = l;
+
+    /*-
+     * Reconstruct length from encoded length field
+     * and initialize it with counter value.
+     */
+    n = 0;
+    for (i = 15 - l; i < 15; i++) {
+        n |= sctx->ccm.s390x.nonce.b[i];
+        sctx->ccm.s390x.nonce.b[i] = 0;
+        n <<= 8;
+    }
+    n |= sctx->ccm.s390x.nonce.b[15];
+    sctx->ccm.s390x.nonce.b[15] = 1;
+
+    if (n != len)
+        return 0; /* length mismatch */
+
+    if (enc) {
+        /* Two operations per block plus one for tag encryption */
+        sctx->ccm.s390x.blocks += (((len + 15) >> 4) << 1) + 1;
+        if (sctx->ccm.s390x.blocks > (1ULL << 61))
+            return 0; /* too much data */
+    }
+
+    num = 0;
+    rem = len & 0xf;
+    len &= ~(size_t)0xf;
+
+    if (enc) {
+        /* mac-then-encrypt */
+        if (len)
+            s390x_kmac(in, len, sctx->ccm.s390x.fc, &sctx->ccm.s390x.kmac);
+        if (rem) {
+            for (i = 0; i < rem; i++)
+                sctx->ccm.s390x.kmac.icv.b[i] ^= in[len + i];
+
+            s390x_km(sctx->ccm.s390x.kmac.icv.b, 16,
+                sctx->ccm.s390x.kmac.icv.b,
+                sctx->ccm.s390x.fc, sctx->ccm.s390x.kmac.k);
+        }
+
+        CRYPTO_ctr128_encrypt_ctr32(in, out, len + rem, &sctx->ccm.ks.ks,
+            sctx->ccm.s390x.nonce.b, sctx->ccm.s390x.buf.b,
+            &num, (ctr128_f)AES_ctr32_encrypt);
+    } else {
+        /* decrypt-then-mac */
+        CRYPTO_ctr128_encrypt_ctr32(in, out, len + rem, &sctx->ccm.ks.ks,
+            sctx->ccm.s390x.nonce.b, sctx->ccm.s390x.buf.b,
+            &num, (ctr128_f)AES_ctr32_encrypt);
+
+        if (len)
+            s390x_kmac(out, len, sctx->ccm.s390x.fc, &sctx->ccm.s390x.kmac);
+        if (rem) {
+            for (i = 0; i < rem; i++)
+                sctx->ccm.s390x.kmac.icv.b[i] ^= out[len + i];
+
+            s390x_km(sctx->ccm.s390x.kmac.icv.b, 16,
+                sctx->ccm.s390x.kmac.icv.b,
+                sctx->ccm.s390x.fc, sctx->ccm.s390x.kmac.k);
+        }
+    }
+    /* encrypt tag */
+    for (i = 15 - l; i < 16; i++)
+        sctx->ccm.s390x.nonce.b[i] = 0;
+
+    s390x_km(sctx->ccm.s390x.nonce.b, 16, sctx->ccm.s390x.buf.b,
+        sctx->ccm.s390x.fc, sctx->ccm.s390x.kmac.k);
+    sctx->ccm.s390x.kmac.icv.g[0] ^= sctx->ccm.s390x.buf.g[0];
+    sctx->ccm.s390x.kmac.icv.g[1] ^= sctx->ccm.s390x.buf.g[1];
+
+    sctx->ccm.s390x.nonce.b[0] = flags; /* restore flags field */
+    return 1;
+}
+
+static int s390x_aes_ccm_gettag(PROV_CCM_CTX *ctx,
+    unsigned char *tag, size_t tlen)
+{
+    PROV_AES_CCM_CTX *sctx = (PROV_AES_CCM_CTX *)ctx;
+
+    if (tlen > ctx->m)
+        return 0;
+    memcpy(tag, sctx->ccm.s390x.kmac.icv.b, tlen);
+    return 1;
+}
+
+static int s390x_aes_ccm_auth_encrypt(PROV_CCM_CTX *ctx,
+    const unsigned char *in,
+    unsigned char *out, size_t len,
+    unsigned char *tag, size_t taglen)
+{
+    int rv;
+
+    rv = s390x_aes_ccm_auth_encdec(ctx, in, out, len, 1);
+    if (rv && tag != NULL)
+        rv = s390x_aes_ccm_gettag(ctx, tag, taglen);
+    return rv;
+}
+
+static int s390x_aes_ccm_auth_decrypt(PROV_CCM_CTX *ctx,
+    const unsigned char *in,
+    unsigned char *out, size_t len,
+    unsigned char *expected_tag,
+    size_t taglen)
+{
+    int rv = 0;
+    PROV_AES_CCM_CTX *sctx = (PROV_AES_CCM_CTX *)ctx;
+
+    rv = s390x_aes_ccm_auth_encdec(ctx, in, out, len, 0);
+    if (rv) {
+        if (CRYPTO_memcmp(sctx->ccm.s390x.kmac.icv.b, expected_tag, ctx->m) != 0)
+            rv = 0;
+    }
+    if (rv == 0)
+        OPENSSL_cleanse(out, len);
+    return rv;
+}
+
+static const PROV_CCM_HW s390x_aes_ccm = {
+    s390x_aes_ccm_initkey,
+    s390x_aes_ccm_setiv,
+    s390x_aes_ccm_setaad,
+    s390x_aes_ccm_auth_encrypt,
+    s390x_aes_ccm_auth_decrypt,
+    s390x_aes_ccm_gettag
+};
+
+const PROV_CCM_HW *ossl_prov_aes_hw_ccm_s390x(size_t keybits)
+{
+    if ((keybits == 128 && S390X_aes_128_ccm_CAPABLE)
+        || (keybits == 192 && S390X_aes_192_ccm_CAPABLE)
+        || (keybits == 256 && S390X_aes_256_ccm_CAPABLE))
+        return &s390x_aes_ccm;
+    return NULL;
+}
+
+#endif
+
+/* MODES: XTS */
+
+#if defined(AES_XTS_S390X)
+
+int s390x_aes_xts_cipher_stream(PROV_AES_XTS_CTX *xctx,
+    unsigned char *out, size_t *outl,
+    const unsigned char *in, size_t inl)
+{
+    S390X_KM_XTS_PARAMS *km = &xctx->plat.s390x.param.km;
+    unsigned char *param = (unsigned char *)km + xctx->plat.s390x.offset;
+    unsigned int fc = xctx->plat.s390x.fc;
+    unsigned char tmp[2][AES_BLOCK_SIZE];
+    unsigned char nap_n1[AES_BLOCK_SIZE];
+    unsigned char drop[AES_BLOCK_SIZE];
+    size_t len_incomplete, len_complete;
+
+    len_incomplete = inl % AES_BLOCK_SIZE;
+    len_complete = (len_incomplete == 0) ? inl : (inl / AES_BLOCK_SIZE - 1) * AES_BLOCK_SIZE;
+
+    if (len_complete > 0)
+        s390x_km(in, len_complete, out, fc, param);
+    if (len_incomplete == 0)
+        goto out;
+
+    memcpy(tmp, in + len_complete, AES_BLOCK_SIZE + len_incomplete);
+    /* swap NAP for decrypt */
+    if (fc & S390X_DECRYPT) {
+        memcpy(nap_n1, km->nap, AES_BLOCK_SIZE);
+        s390x_km(tmp[0], AES_BLOCK_SIZE, drop, fc, param);
+    }
+    s390x_km(tmp[0], AES_BLOCK_SIZE, tmp[0], fc, param);
+    if (fc & S390X_DECRYPT)
+        memcpy(km->nap, nap_n1, AES_BLOCK_SIZE);
+
+    memcpy(tmp[1] + len_incomplete, tmp[0] + len_incomplete,
+        AES_BLOCK_SIZE - len_incomplete);
+    s390x_km(tmp[1], AES_BLOCK_SIZE, out + len_complete, fc, param);
+    memcpy(out + len_complete + AES_BLOCK_SIZE, tmp[0], len_incomplete);
+
+    /* do not expose temporary data */
+    OPENSSL_cleanse(tmp, sizeof(tmp));
+out:
+    memcpy(xctx->base.iv, km->tweak, AES_BLOCK_SIZE);
+    *outl = inl;
+
+    return 1;
+}
+
+static int cipher_hw_aes_xts_s390x_initkey(PROV_CIPHER_CTX *ctx,
+    const unsigned char *key, size_t keylen)
+{
+    PROV_AES_XTS_CTX *xctx = (PROV_AES_XTS_CTX *)ctx;
+    S390X_KM_XTS_PARAMS *km = &xctx->plat.s390x.param.km;
+    unsigned int fc, offs;
+    unsigned int dec = 0;
+    int supported = 0;
+
+    switch (keylen) {
+    case 128 / 8 * 2:
+        fc = S390X_XTS_AES_128_MSA10;
+        offs = 32;
+        break;
+    case 256 / 8 * 2:
+        fc = S390X_XTS_AES_256_MSA10;
+        offs = 0;
+        break;
+    default:
+        fc = 0;
+        break;
+    }
+
+    if (fc != 0)
+        supported = (OPENSSL_s390xcap_P.km[1] && S390X_CAPBIT(fc));
+    if (!supported) {
+        xctx->plat.s390x.fc = 0;
+        xctx->plat.s390x.offset = 0;
+        return 0;
+    }
+
+    if (xctx->base.iv_set) {
+        if (xctx->base.ivlen > sizeof(km->tweak)) {
+            ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_IV_LENGTH);
+            return 0;
+        }
+        memcpy(km->tweak, xctx->base.iv, xctx->base.ivlen);
+        xctx->plat.s390x.iv_set = 1;
+    }
+
+    if (key != NULL) {
+        memcpy(km->key + offs, key, keylen);
+        xctx->plat.s390x.key_set = 1;
+    }
+
+    if (xctx->base.enc == 0)
+        dec = S390X_DECRYPT;
+
+    xctx->plat.s390x.fc = fc | dec;
+    xctx->plat.s390x.offset = offs;
+
+    memset(km->nap, 0, sizeof(km->nap));
+    km->nap[0] = 0x1;
+
+    return 1;
+}
+
+static void cipher_hw_aes_xts_s390x_copyctx(PROV_CIPHER_CTX *dst,
+    const PROV_CIPHER_CTX *src)
+{
+    PROV_AES_XTS_CTX *sctx = (PROV_AES_XTS_CTX *)src;
+    PROV_AES_XTS_CTX *dctx = (PROV_AES_XTS_CTX *)dst;
+
+    *dctx = *sctx;
+    dctx->xts.key1 = NULL;
+    dctx->xts.key2 = NULL;
+}
+
+static const PROV_CIPHER_HW aes_xts_s390x = {
+    cipher_hw_aes_xts_s390x_initkey,
+    NULL,
+    cipher_hw_aes_xts_s390x_copyctx
+};
+
+const PROV_CIPHER_HW *ossl_prov_cipher_hw_aes_xts_s390x(size_t keybits)
+{
+    switch (keybits) {
+    case (128 * 2):
+        if (OPENSSL_s390xcap_P.km[1] && S390X_CAPBIT(S390X_XTS_AES_128_MSA10))
+            return &aes_xts_s390x;
+        break;
+    case (256 * 2):
+        if (OPENSSL_s390xcap_P.km[1] && S390X_CAPBIT(S390X_XTS_AES_256_MSA10))
+            return &aes_xts_s390x;
+        break;
+    default:
+        break;
+    }
+
+    return NULL;
+}
+
 #endif
diff --git a/providers/implementations/ciphers/cipher_aes_xts.h b/providers/implementations/ciphers/cipher_aes_xts.h
index 5a626144e0..9fa0624dfc 100644
--- a/providers/implementations/ciphers/cipher_aes_xts.h
+++ b/providers/implementations/ciphers/cipher_aes_xts.h
@@ -86,6 +86,7 @@ const PROV_CIPHER_HW *ossl_prov_cipher_hw_aes_xts_rv64i(void);
 int s390x_aes_xts_cipher_stream(PROV_AES_XTS_CTX *xctx,
     unsigned char *out, size_t *outl,
     const unsigned char *in, size_t inl);
+const PROV_CIPHER_HW *ossl_prov_cipher_hw_aes_xts_s390x(size_t keybits);
 #endif

 const PROV_CIPHER_HW *ossl_prov_cipher_hw_aes_xts(size_t keybits);
diff --git a/providers/implementations/ciphers/cipher_aes_xts_hw.c b/providers/implementations/ciphers/cipher_aes_xts_hw.c
index 537786a7aa..e584f12051 100644
--- a/providers/implementations/ciphers/cipher_aes_xts_hw.c
+++ b/providers/implementations/ciphers/cipher_aes_xts_hw.c
@@ -142,144 +142,6 @@ static const PROV_CIPHER_HW *ossl_prov_cipher_hw_aes_xts_t4()
     return NULL;
 }

-#elif defined(AES_XTS_S390X)
-
-int s390x_aes_xts_cipher_stream(PROV_AES_XTS_CTX *xctx,
-    unsigned char *out, size_t *outl,
-    const unsigned char *in, size_t inl)
-{
-    S390X_KM_XTS_PARAMS *km = &xctx->plat.s390x.param.km;
-    unsigned char *param = (unsigned char *)km + xctx->plat.s390x.offset;
-    unsigned int fc = xctx->plat.s390x.fc;
-    unsigned char tmp[2][AES_BLOCK_SIZE];
-    unsigned char nap_n1[AES_BLOCK_SIZE];
-    unsigned char drop[AES_BLOCK_SIZE];
-    size_t len_incomplete, len_complete;
-
-    len_incomplete = inl % AES_BLOCK_SIZE;
-    len_complete = (len_incomplete == 0) ? inl : (inl / AES_BLOCK_SIZE - 1) * AES_BLOCK_SIZE;
-
-    if (len_complete > 0)
-        s390x_km(in, len_complete, out, fc, param);
-    if (len_incomplete == 0)
-        goto out;
-
-    memcpy(tmp, in + len_complete, AES_BLOCK_SIZE + len_incomplete);
-    /* swap NAP for decrypt */
-    if (fc & S390X_DECRYPT) {
-        memcpy(nap_n1, km->nap, AES_BLOCK_SIZE);
-        s390x_km(tmp[0], AES_BLOCK_SIZE, drop, fc, param);
-    }
-    s390x_km(tmp[0], AES_BLOCK_SIZE, tmp[0], fc, param);
-    if (fc & S390X_DECRYPT)
-        memcpy(km->nap, nap_n1, AES_BLOCK_SIZE);
-
-    memcpy(tmp[1] + len_incomplete, tmp[0] + len_incomplete,
-        AES_BLOCK_SIZE - len_incomplete);
-    s390x_km(tmp[1], AES_BLOCK_SIZE, out + len_complete, fc, param);
-    memcpy(out + len_complete + AES_BLOCK_SIZE, tmp[0], len_incomplete);
-
-    /* do not expose temporary data */
-    OPENSSL_cleanse(tmp, sizeof(tmp));
-out:
-    memcpy(xctx->base.iv, km->tweak, AES_BLOCK_SIZE);
-    *outl = inl;
-
-    return 1;
-}
-
-static int cipher_hw_aes_xts_s390x_initkey(PROV_CIPHER_CTX *ctx,
-    const unsigned char *key, size_t keylen)
-{
-    PROV_AES_XTS_CTX *xctx = (PROV_AES_XTS_CTX *)ctx;
-    S390X_KM_XTS_PARAMS *km = &xctx->plat.s390x.param.km;
-    unsigned int fc, offs;
-    unsigned int dec = 0;
-    int supported = 0;
-
-    switch (keylen) {
-    case 128 / 8 * 2:
-        fc = S390X_XTS_AES_128_MSA10;
-        offs = 32;
-        break;
-    case 256 / 8 * 2:
-        fc = S390X_XTS_AES_256_MSA10;
-        offs = 0;
-        break;
-    default:
-        fc = 0;
-        break;
-    }
-
-    if (fc != 0)
-        supported = (OPENSSL_s390xcap_P.km[1] && S390X_CAPBIT(fc));
-    if (!supported) {
-        xctx->plat.s390x.fc = 0;
-        xctx->plat.s390x.offset = 0;
-        return 0;
-    }
-
-    if (xctx->base.iv_set) {
-        if (xctx->base.ivlen > sizeof(km->tweak)) {
-            ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_IV_LENGTH);
-            return 0;
-        }
-        memcpy(km->tweak, xctx->base.iv, xctx->base.ivlen);
-        xctx->plat.s390x.iv_set = 1;
-    }
-
-    if (key != NULL) {
-        memcpy(km->key + offs, key, keylen);
-        xctx->plat.s390x.key_set = 1;
-    }
-
-    if (xctx->base.enc == 0)
-        dec = S390X_DECRYPT;
-
-    xctx->plat.s390x.fc = fc | dec;
-    xctx->plat.s390x.offset = offs;
-
-    memset(km->nap, 0, sizeof(km->nap));
-    km->nap[0] = 0x1;
-
-    return 1;
-}
-
-static void cipher_hw_aes_xts_s390x_copyctx(PROV_CIPHER_CTX *dst,
-    const PROV_CIPHER_CTX *src)
-{
-    PROV_AES_XTS_CTX *sctx = (PROV_AES_XTS_CTX *)src;
-    PROV_AES_XTS_CTX *dctx = (PROV_AES_XTS_CTX *)dst;
-
-    *dctx = *sctx;
-    dctx->xts.key1 = NULL;
-    dctx->xts.key2 = NULL;
-}
-
-static const PROV_CIPHER_HW aes_xts_s390x = {
-    cipher_hw_aes_xts_s390x_initkey,
-    NULL,
-    cipher_hw_aes_xts_s390x_copyctx
-};
-
-static const PROV_CIPHER_HW *ossl_prov_cipher_hw_aes_xts_s390x(size_t keybits)
-{
-    switch (keybits) {
-    case (128 * 2):
-        if (OPENSSL_s390xcap_P.km[1] && S390X_CAPBIT(S390X_XTS_AES_128_MSA10))
-            return &aes_xts_s390x;
-        break;
-    case (256 * 2):
-        if (OPENSSL_s390xcap_P.km[1] && S390X_CAPBIT(S390X_XTS_AES_256_MSA10))
-            return &aes_xts_s390x;
-        break;
-    default:
-        break;
-    }
-
-    return NULL;
-}
-
 #endif

 static const PROV_CIPHER_HW aes_generic_xts = {