mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-07-05 17:50:48 +02:00
Merge pull request #9745 from dgarske/stm32_hmac
Support for STM32 HMAC hardware
This commit is contained in:
@@ -252,6 +252,7 @@ GOAHEAD_WS
|
||||
HAL_RTC_MODULE_ENABLED
|
||||
HARDWARE_CACHE_COHERENCY
|
||||
HASH_AlgoMode_HASH
|
||||
HASH_AlgoMode_HMAC
|
||||
HASH_BYTE_SWAP
|
||||
HASH_CR_LKEY
|
||||
HASH_DIGEST
|
||||
@@ -432,6 +433,7 @@ NO_SESSION_CACHE_ROW_LOCK
|
||||
NO_SKID
|
||||
NO_SKIP_PREVIEW
|
||||
NO_STDIO_FGETS_REMAP
|
||||
NO_STM32_HMAC
|
||||
NO_TKERNEL_MEM_POOL
|
||||
NO_TLSX_PSKKEM_PLAIN_ANNOUNCE
|
||||
NO_VERIFY_OID
|
||||
|
||||
@@ -533,6 +533,59 @@ int wc_HmacSetKey_ex(Hmac* hmac, int type, const byte* key, word32 length,
|
||||
return 0;
|
||||
#else
|
||||
|
||||
#if defined(STM32_HASH) && defined(STM32_HMAC)
|
||||
{
|
||||
word32 stmAlgo, stmBlockSize, stmDigestSize;
|
||||
/* Check if this hash type is supported by STM32 HMAC hardware */
|
||||
if (wc_Stm32_Hmac_GetAlgoInfo(type, &stmAlgo, &stmBlockSize,
|
||||
&stmDigestSize) == 0) {
|
||||
/* Cache algo info for Update/Final */
|
||||
hmac->stmAlgo = stmAlgo;
|
||||
hmac->stmBlockSize = stmBlockSize;
|
||||
hmac->stmDigestSize = stmDigestSize;
|
||||
|
||||
/* Store raw key in ipad (unused in HW HMAC mode).
|
||||
* Pre-hash if longer than hash block size. */
|
||||
if (length <= stmBlockSize) {
|
||||
if (key != NULL) {
|
||||
XMEMCPY(hmac->ipad, key, length);
|
||||
}
|
||||
hmac->stmKeyLen = length;
|
||||
}
|
||||
else {
|
||||
/* Pre-hash long key using stmCtx (re-initialized below) */
|
||||
wc_Stm32_Hash_Init(&hmac->stmCtx);
|
||||
ret = wolfSSL_CryptHwMutexLock();
|
||||
if (ret == 0) {
|
||||
ret = wc_Stm32_Hash_Update(&hmac->stmCtx, stmAlgo,
|
||||
key, length, stmBlockSize);
|
||||
if (ret == 0) {
|
||||
ret = wc_Stm32_Hash_Final(&hmac->stmCtx, stmAlgo,
|
||||
(byte*)hmac->ipad, stmDigestSize);
|
||||
}
|
||||
wolfSSL_CryptHwMutexUnLock();
|
||||
}
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
hmac->stmKeyLen = stmDigestSize;
|
||||
}
|
||||
|
||||
/* HW HMAC Phase 1: feed key */
|
||||
ret = wolfSSL_CryptHwMutexLock();
|
||||
if (ret == 0) {
|
||||
ret = wc_Stm32_Hmac_SetKey(&hmac->stmCtx, type,
|
||||
(const byte*)hmac->ipad, hmac->stmKeyLen);
|
||||
wolfSSL_CryptHwMutexUnLock();
|
||||
}
|
||||
if (ret == 0) {
|
||||
hmac->innerHashKeyed = WC_HMAC_INNER_HASH_KEYED_DEV;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
/* Unsupported algo falls through to software */
|
||||
}
|
||||
#endif /* STM32_HASH && STM32_HMAC */
|
||||
|
||||
ip = (byte*)hmac->ipad;
|
||||
op = (byte*)hmac->opad;
|
||||
|
||||
@@ -853,6 +906,18 @@ int wc_HmacUpdate(Hmac* hmac, const byte* msg, word32 length)
|
||||
}
|
||||
#endif /* WOLFSSL_ASYNC_CRYPT */
|
||||
|
||||
#if defined(STM32_HASH) && defined(STM32_HMAC)
|
||||
if (hmac->innerHashKeyed == WC_HMAC_INNER_HASH_KEYED_DEV) {
|
||||
ret = wolfSSL_CryptHwMutexLock();
|
||||
if (ret == 0) {
|
||||
ret = wc_Stm32_Hmac_Update(&hmac->stmCtx, hmac->stmAlgo,
|
||||
msg, length, hmac->stmBlockSize);
|
||||
wolfSSL_CryptHwMutexUnLock();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif /* STM32_HASH && STM32_HMAC */
|
||||
|
||||
if (!hmac->innerHashKeyed) {
|
||||
#ifndef WOLFSSL_HMAC_COPY_HASH
|
||||
ret = HmacKeyHashUpdate(hmac->macType, &hmac->hash, (byte*)hmac->ipad);
|
||||
@@ -970,6 +1035,25 @@ int wc_HmacFinal(Hmac* hmac, byte* hash)
|
||||
}
|
||||
#endif /* WOLFSSL_ASYNC_CRYPT */
|
||||
|
||||
#if defined(STM32_HASH) && defined(STM32_HMAC)
|
||||
if (hmac->innerHashKeyed == WC_HMAC_INNER_HASH_KEYED_DEV) {
|
||||
ret = wolfSSL_CryptHwMutexLock();
|
||||
if (ret == 0) {
|
||||
ret = wc_Stm32_Hmac_Final(&hmac->stmCtx, hmac->stmAlgo,
|
||||
(const byte*)hmac->ipad, hmac->stmKeyLen, hash,
|
||||
hmac->stmDigestSize);
|
||||
/* Re-run Phase 1 so HMAC is ready for next Update/Final cycle
|
||||
* (needed for PRF/HKDF loops that reuse the same key) */
|
||||
if (ret == 0) {
|
||||
ret = wc_Stm32_Hmac_SetKey(&hmac->stmCtx, hmac->macType,
|
||||
(const byte*)hmac->ipad, hmac->stmKeyLen);
|
||||
}
|
||||
wolfSSL_CryptHwMutexUnLock();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif /* STM32_HASH && STM32_HMAC */
|
||||
|
||||
if (!hmac->innerHashKeyed) {
|
||||
#ifndef WOLFSSL_HMAC_COPY_HASH
|
||||
ret = HmacKeyHashUpdate(hmac->macType, &hmac->hash, (byte*)hmac->ipad);
|
||||
|
||||
@@ -41,6 +41,7 @@ To disable portions of the hardware acceleration you can optionally define:
|
||||
#define NO_STM32_RNG
|
||||
#define NO_STM32_CRYPTO
|
||||
#define NO_STM32_HASH
|
||||
#define NO_STM32_HMAC
|
||||
```
|
||||
|
||||
### Coding
|
||||
|
||||
+212
-20
@@ -162,12 +162,13 @@ static void wc_Stm32_Hash_SaveContext(STM32_HASH_Context* ctx)
|
||||
#endif
|
||||
}
|
||||
|
||||
static void wc_Stm32_Hash_RestoreContext(STM32_HASH_Context* ctx, int algo)
|
||||
static void wc_Stm32_Hash_RestoreContext(STM32_HASH_Context* ctx, word32 algo,
|
||||
word32 mode)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (ctx->HASH_CR == 0) {
|
||||
/* init content */
|
||||
/* init context */
|
||||
|
||||
#if defined(HASH_IMR_DINIE) && defined(HASH_IMR_DCIE)
|
||||
/* Disable IRQ's - wolfSSL does not use the HASH/RNG IRQ
|
||||
@@ -175,37 +176,31 @@ static void wc_Stm32_Hash_RestoreContext(STM32_HASH_Context* ctx, int algo)
|
||||
HASH->IMR &= ~(HASH_IMR_DINIE | HASH_IMR_DCIE);
|
||||
#endif
|
||||
|
||||
/* reset the control register */
|
||||
HASH->CR &= ~(HASH_CR_ALGO | HASH_CR_MODE | HASH_CR_DATATYPE
|
||||
#ifdef HASH_CR_LKEY
|
||||
| HASH_CR_LKEY
|
||||
#endif
|
||||
);
|
||||
|
||||
/* configure algorithm, mode and data type */
|
||||
HASH->CR |= (algo | HASH_ALGOMODE_HASH | HASH_DATATYPE_8B);
|
||||
|
||||
/* reset HASH processor */
|
||||
HASH->CR |= HASH_CR_INIT;
|
||||
/* Configure algorithm, mode, data type and initialize HASH processor.
|
||||
* INIT must be written in the same register write as ALGO because
|
||||
* setting INIT resets ALGO bits to their default value (MD5). */
|
||||
HASH->CR = (algo | mode | HASH_DATATYPE_8B | HASH_CR_INIT);
|
||||
|
||||
/* by default mark all bits valid */
|
||||
wc_Stm32_Hash_NumValidBits(0);
|
||||
|
||||
#ifdef DEBUG_STM32_HASH
|
||||
printf("STM Init algo %x\n", algo);
|
||||
printf("STM Init algo %x, mode %x, CR %lx, SR %lx\n",
|
||||
(unsigned int)algo, (unsigned int)mode,
|
||||
HASH->CR, HASH->SR);
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
/* restore context registers */
|
||||
HASH->IMR = ctx->HASH_IMR;
|
||||
HASH->STR = ctx->HASH_STR;
|
||||
HASH->CR = ctx->HASH_CR;
|
||||
#ifdef STM32_HASH_SHA3
|
||||
HASH->SHA3CFGR = ctx->SHA3CFGR;
|
||||
#endif
|
||||
|
||||
/* Initialize the hash processor */
|
||||
HASH->CR |= HASH_CR_INIT;
|
||||
/* Restore CR with INIT in a single write - setting INIT resets ALGO
|
||||
* bits, so we must include the saved CR value in the same write. */
|
||||
HASH->CR = ctx->HASH_CR | HASH_CR_INIT;
|
||||
|
||||
/* continue restoring context registers */
|
||||
for (i=0; i<HASH_CR_SIZE; i++) {
|
||||
@@ -363,7 +358,7 @@ int wc_Stm32_Hash_Update(STM32_HASH_Context* stmCtx, word32 algo,
|
||||
STM32_HASH_CLOCK_ENABLE(stmCtx);
|
||||
|
||||
/* restore hash context or init as new hash */
|
||||
wc_Stm32_Hash_RestoreContext(stmCtx, algo);
|
||||
wc_Stm32_Hash_RestoreContext(stmCtx, algo, HASH_ALGOMODE_HASH);
|
||||
|
||||
/* write blocks to FIFO */
|
||||
while (len) {
|
||||
@@ -417,7 +412,7 @@ int wc_Stm32_Hash_Final(STM32_HASH_Context* stmCtx, word32 algo,
|
||||
STM32_HASH_CLOCK_ENABLE(stmCtx);
|
||||
|
||||
/* restore hash context or init as new hash */
|
||||
wc_Stm32_Hash_RestoreContext(stmCtx, algo);
|
||||
wc_Stm32_Hash_RestoreContext(stmCtx, algo, HASH_ALGOMODE_HASH);
|
||||
|
||||
/* finish reading any trailing bytes into FIFO */
|
||||
if (stmCtx->buffLen > 0) {
|
||||
@@ -444,6 +439,203 @@ int wc_Stm32_Hash_Final(STM32_HASH_Context* stmCtx, word32 algo,
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if defined(STM32_HMAC) && !defined(NO_HMAC)
|
||||
|
||||
/* STM32 Port HMAC Functions */
|
||||
#include <wolfssl/wolfcrypt/hmac.h>
|
||||
|
||||
int wc_Stm32_Hmac_GetAlgoInfo(int macType, word32* algo, word32* blockSize,
|
||||
word32* digestSize)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
switch (macType) {
|
||||
#if !defined(NO_MD5) && !defined(STM32_NOMD5)
|
||||
case WC_MD5:
|
||||
if (algo) *algo = HASH_AlgoSelection_MD5;
|
||||
if (blockSize) *blockSize = WC_MD5_BLOCK_SIZE;
|
||||
if (digestSize) *digestSize = WC_MD5_DIGEST_SIZE;
|
||||
break;
|
||||
#endif
|
||||
#ifndef NO_SHA
|
||||
case WC_SHA:
|
||||
if (algo) *algo = HASH_AlgoSelection_SHA1;
|
||||
if (blockSize) *blockSize = WC_SHA_BLOCK_SIZE;
|
||||
if (digestSize) *digestSize = WC_SHA_DIGEST_SIZE;
|
||||
break;
|
||||
#endif
|
||||
#ifdef WOLFSSL_SHA224
|
||||
case WC_SHA224:
|
||||
if (algo) *algo = HASH_AlgoSelection_SHA224;
|
||||
if (blockSize) *blockSize = WC_SHA224_BLOCK_SIZE;
|
||||
if (digestSize) *digestSize = WC_SHA224_DIGEST_SIZE;
|
||||
break;
|
||||
#endif
|
||||
#ifndef NO_SHA256
|
||||
case WC_SHA256:
|
||||
if (algo) *algo = HASH_AlgoSelection_SHA256;
|
||||
if (blockSize) *blockSize = WC_SHA256_BLOCK_SIZE;
|
||||
if (digestSize) *digestSize = WC_SHA256_DIGEST_SIZE;
|
||||
break;
|
||||
#endif
|
||||
#if defined(STM32_HASH_SHA384) && defined(WOLFSSL_SHA384)
|
||||
case WC_SHA384:
|
||||
if (algo) *algo = HASH_ALGOSELECTION_SHA384;
|
||||
if (blockSize) *blockSize = WC_SHA384_BLOCK_SIZE;
|
||||
if (digestSize) *digestSize = WC_SHA384_DIGEST_SIZE;
|
||||
break;
|
||||
#endif
|
||||
#if defined(STM32_HASH_SHA512) && defined(WOLFSSL_SHA512)
|
||||
case WC_SHA512:
|
||||
if (algo) *algo = HASH_ALGOSELECTION_SHA512;
|
||||
if (blockSize) *blockSize = WC_SHA512_BLOCK_SIZE;
|
||||
if (digestSize) *digestSize = WC_SHA512_DIGEST_SIZE;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
ret = BAD_FUNC_ARG;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void wc_Stm32_Hmac_FeedKey(const byte* key, word32 keySz)
|
||||
{
|
||||
word32 i, blocks;
|
||||
word32 tmp;
|
||||
|
||||
/* feed key words into HASH->DIN */
|
||||
blocks = keySz / STM32_HASH_REG_SIZE;
|
||||
for (i = 0; i < blocks; i++) {
|
||||
XMEMCPY(&tmp, key + (i * STM32_HASH_REG_SIZE), STM32_HASH_REG_SIZE);
|
||||
HASH->DIN = tmp;
|
||||
}
|
||||
/* handle remaining bytes in last partial word */
|
||||
if (keySz % STM32_HASH_REG_SIZE) {
|
||||
tmp = 0;
|
||||
XMEMCPY(&tmp, key + (blocks * STM32_HASH_REG_SIZE),
|
||||
keySz % STM32_HASH_REG_SIZE);
|
||||
HASH->DIN = tmp;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_STM32_HASH
|
||||
printf("STM HMAC FeedKey %d bytes\n", (int)keySz);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/* STM32 HMAC Exposed Functions */
|
||||
|
||||
int wc_Stm32_Hmac_SetKey(STM32_HASH_Context* stmCtx, int macType,
|
||||
const byte* key, word32 keySz)
|
||||
{
|
||||
int ret;
|
||||
word32 algo, blockSize, digestSize;
|
||||
word32 mode;
|
||||
|
||||
if (stmCtx == NULL || key == NULL)
|
||||
return BAD_FUNC_ARG;
|
||||
|
||||
ret = wc_Stm32_Hmac_GetAlgoInfo(macType, &algo, &blockSize, &digestSize);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
#ifdef DEBUG_STM32_HASH
|
||||
printf("STM HMAC SetKey: macType %d, keySz %d\n", macType, (int)keySz);
|
||||
#endif
|
||||
|
||||
/* clear context for fresh HMAC */
|
||||
wc_Stm32_Hash_Init(stmCtx);
|
||||
|
||||
/* turn on hash clock */
|
||||
STM32_HASH_CLOCK_ENABLE(stmCtx);
|
||||
|
||||
/* initialize hardware for HMAC mode.
|
||||
* Keys are always pre-hashed in software before reaching this point
|
||||
* (see hmac.c), so keySz will always be <= blockSize here. */
|
||||
mode = HASH_ALGOMODE_HMAC;
|
||||
wc_Stm32_Hash_RestoreContext(stmCtx, algo, mode);
|
||||
|
||||
/* Phase 1: Feed key into HASH->DIN */
|
||||
wc_Stm32_Hmac_FeedKey(key, keySz);
|
||||
|
||||
/* set number of valid bits in last word and trigger DCAL */
|
||||
wc_Stm32_Hash_NumValidBits(keySz);
|
||||
HASH->STR |= HASH_STR_DCAL;
|
||||
|
||||
/* wait for data input ready (phase 1 complete) */
|
||||
ret = wc_Stm32_Hash_WaitDataReady(stmCtx);
|
||||
|
||||
if (ret == 0) {
|
||||
/* save context for context switching */
|
||||
wc_Stm32_Hash_SaveContext(stmCtx);
|
||||
}
|
||||
|
||||
/* turn off hash clock */
|
||||
STM32_HASH_CLOCK_DISABLE(stmCtx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int wc_Stm32_Hmac_Final(STM32_HASH_Context* stmCtx, word32 algo,
|
||||
const byte* key, word32 keySz, byte* hash, word32 digestSize)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (stmCtx == NULL || key == NULL || hash == NULL)
|
||||
return BAD_FUNC_ARG;
|
||||
|
||||
#ifdef DEBUG_STM32_HASH
|
||||
printf("STM HMAC Final: algo %x, keySz %d, buffLen %d, fifoBytes %d\n",
|
||||
(unsigned int)algo, (int)keySz, (int)stmCtx->buffLen,
|
||||
(int)stmCtx->fifoBytes);
|
||||
#endif
|
||||
|
||||
/* turn on hash clock */
|
||||
STM32_HASH_CLOCK_ENABLE(stmCtx);
|
||||
|
||||
/* restore HMAC context */
|
||||
wc_Stm32_Hash_RestoreContext(stmCtx, algo, HASH_ALGOMODE_HMAC);
|
||||
|
||||
/* finish reading any trailing bytes into FIFO */
|
||||
if (stmCtx->buffLen > 0) {
|
||||
wc_Stm32_Hash_Data(stmCtx, stmCtx->buffLen);
|
||||
}
|
||||
|
||||
/* Phase 2 complete: set valid bits and trigger DCAL */
|
||||
wc_Stm32_Hash_NumValidBits(stmCtx->loLen + stmCtx->buffLen);
|
||||
HASH->STR |= HASH_STR_DCAL;
|
||||
|
||||
/* wait for data input ready (phase 2 complete, ready for phase 3) */
|
||||
ret = wc_Stm32_Hash_WaitDataReady(stmCtx);
|
||||
if (ret != 0) {
|
||||
STM32_HASH_CLOCK_DISABLE(stmCtx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Phase 3: Feed key again into HASH->DIN */
|
||||
wc_Stm32_Hmac_FeedKey(key, keySz);
|
||||
|
||||
/* set valid bits for key and trigger DCAL */
|
||||
wc_Stm32_Hash_NumValidBits(keySz);
|
||||
HASH->STR |= HASH_STR_DCAL;
|
||||
|
||||
/* wait for hash done (digest computation complete) */
|
||||
ret = wc_Stm32_Hash_WaitCalcComp(stmCtx);
|
||||
if (ret == 0) {
|
||||
/* read message digest */
|
||||
wc_Stm32_Hash_GetDigest(hash, digestSize);
|
||||
}
|
||||
|
||||
/* turn off hash clock */
|
||||
STM32_HASH_CLOCK_DISABLE(stmCtx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif /* STM32_HMAC && !NO_HMAC */
|
||||
|
||||
#endif /* STM32_HASH */
|
||||
|
||||
|
||||
|
||||
@@ -158,6 +158,13 @@ struct Hmac {
|
||||
#if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLF_CRYPTO_CB)
|
||||
word16 keyLen; /* hmac key length (key in ipad) */
|
||||
#endif
|
||||
#if defined(STM32_HASH) && defined(STM32_HMAC)
|
||||
STM32_HASH_Context stmCtx;
|
||||
word32 stmAlgo; /* cached STM32 HASH algo selection */
|
||||
word32 stmBlockSize; /* cached hash block size */
|
||||
word32 stmDigestSize; /* cached digest size */
|
||||
word32 stmKeyLen; /* key length (raw key stored in ipad) */
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifndef WC_HMAC_TYPE_DEFINED
|
||||
|
||||
@@ -76,6 +76,9 @@
|
||||
#if !defined(HASH_ALGOMODE_HASH) && defined(HASH_AlgoMode_HASH)
|
||||
#define HASH_ALGOMODE_HASH HASH_AlgoMode_HASH
|
||||
#endif
|
||||
#if !defined(HASH_ALGOMODE_HMAC) && defined(HASH_AlgoMode_HMAC)
|
||||
#define HASH_ALGOMODE_HMAC HASH_AlgoMode_HMAC
|
||||
#endif
|
||||
#if !defined(HASH_DATATYPE_8B)
|
||||
#if defined(HASH_DataType_8b)
|
||||
#define HASH_DATATYPE_8B HASH_DataType_8b
|
||||
@@ -131,6 +134,19 @@ int wc_Stm32_Hash_Update(STM32_HASH_Context* stmCtx, word32 algo,
|
||||
int wc_Stm32_Hash_Final(STM32_HASH_Context* stmCtx, word32 algo,
|
||||
byte* hash, word32 digestSize);
|
||||
|
||||
#ifdef STM32_HMAC
|
||||
/* STM32 Hardware HMAC API */
|
||||
int wc_Stm32_Hmac_GetAlgoInfo(int macType, word32* algo, word32* blockSize,
|
||||
word32* digestSize);
|
||||
int wc_Stm32_Hmac_SetKey(STM32_HASH_Context* stmCtx, int macType,
|
||||
const byte* key, word32 keySz);
|
||||
/* HMAC Update uses the same data feeding as Hash Update */
|
||||
#define wc_Stm32_Hmac_Update(stmCtx, algo, data, len, blockSize) \
|
||||
wc_Stm32_Hash_Update((stmCtx), (algo), (data), (len), (blockSize))
|
||||
int wc_Stm32_Hmac_Final(STM32_HASH_Context* stmCtx, word32 algo,
|
||||
const byte* key, word32 keySz, byte* hash, word32 digestSize);
|
||||
#endif /* STM32_HMAC */
|
||||
|
||||
#endif /* STM32_HASH */
|
||||
|
||||
|
||||
|
||||
@@ -2192,6 +2192,10 @@ extern void uITRON4_free(void *p) ;
|
||||
#undef STM32_HASH
|
||||
#define STM32_HASH
|
||||
#endif
|
||||
#ifndef NO_STM32_HMAC
|
||||
#undef STM32_HMAC
|
||||
#define STM32_HMAC
|
||||
#endif
|
||||
#if !defined(__GNUC__) && !defined(__ICCARM__)
|
||||
#define KEIL_INTRINSICS
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user