static int aes_gcm_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out, const uint8_t *in, size_t len) { EVP_AES_GCM_CTX *gctx = ctx->cipher_data; // If not set up, return error if (!gctx->key_set) { return -1; } if (!gctx->iv_set) { return -1; } if (in) { if (out == NULL) { if (!CRYPTO_gcm128_aad(&gctx->gcm, in, len)) { return -1; } } else if (ctx->encrypt) { if (gctx->ctr) { if (!CRYPTO_gcm128_encrypt_ctr32(&gctx->gcm, &gctx->ks.ks, in, out, len, gctx->ctr)) { return -1; } } else { if (!CRYPTO_gcm128_encrypt(&gctx->gcm, &gctx->ks.ks, in, out, len)) { return -1; } } } else { if (gctx->ctr) { if (!CRYPTO_gcm128_decrypt_ctr32(&gctx->gcm, &gctx->ks.ks, in, out, len, gctx->ctr)) { return -1; } } else { if (!CRYPTO_gcm128_decrypt(&gctx->gcm, &gctx->ks.ks, in, out, len)) { return -1; } } } return len; } else { if (!ctx->encrypt) { if (gctx->taglen < 0 || !CRYPTO_gcm128_finish(&gctx->gcm, ctx->buf, gctx->taglen)) { return -1; } gctx->iv_set = 0; return 0; } CRYPTO_gcm128_tag(&gctx->gcm, ctx->buf, 16); gctx->taglen = 16; // Don't reuse the IV gctx->iv_set = 0; return 0; } }
static int aria_gcm_tls_cipher(EVP_CIPHER_CTX *ctx, unsigned char *out, const unsigned char *in, size_t len) { EVP_ARIA_GCM_CTX *gctx = EVP_C_DATA(EVP_ARIA_GCM_CTX,ctx); int rv = -1; /* Encrypt/decrypt must be performed in place */ if (out != in || len < (EVP_GCM_TLS_EXPLICIT_IV_LEN + EVP_GCM_TLS_TAG_LEN)) return -1; /* * Set IV from start of buffer or generate IV and write to start of * buffer. */ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CIPHER_CTX_encrypting(ctx) ? EVP_CTRL_GCM_IV_GEN : EVP_CTRL_GCM_SET_IV_INV, EVP_GCM_TLS_EXPLICIT_IV_LEN, out) <= 0) goto err; /* Use saved AAD */ if (CRYPTO_gcm128_aad(&gctx->gcm, EVP_CIPHER_CTX_buf_noconst(ctx), gctx->tls_aad_len)) goto err; /* Fix buffer and length to point to payload */ in += EVP_GCM_TLS_EXPLICIT_IV_LEN; out += EVP_GCM_TLS_EXPLICIT_IV_LEN; len -= EVP_GCM_TLS_EXPLICIT_IV_LEN + EVP_GCM_TLS_TAG_LEN; if (EVP_CIPHER_CTX_encrypting(ctx)) { /* Encrypt payload */ if (CRYPTO_gcm128_encrypt(&gctx->gcm, in, out, len)) goto err; out += len; /* Finally write tag */ CRYPTO_gcm128_tag(&gctx->gcm, out, EVP_GCM_TLS_TAG_LEN); rv = len + EVP_GCM_TLS_EXPLICIT_IV_LEN + EVP_GCM_TLS_TAG_LEN; } else { /* Decrypt */ if (CRYPTO_gcm128_decrypt(&gctx->gcm, in, out, len)) goto err; /* Retrieve tag */ CRYPTO_gcm128_tag(&gctx->gcm, EVP_CIPHER_CTX_buf_noconst(ctx), EVP_GCM_TLS_TAG_LEN); /* If tag mismatch wipe buffer */ if (CRYPTO_memcmp(EVP_CIPHER_CTX_buf_noconst(ctx), in + len, EVP_GCM_TLS_TAG_LEN)) { OPENSSL_cleanse(out, len); goto err; } rv = len; } err: gctx->iv_set = 0; gctx->tls_aad_len = -1; return rv; }
static int aes_gcm_cipher(EVP_CIPHER_CTX *ctx, unsigned char *out, const unsigned char *in, size_t len) { EVP_AES_GCM_CTX *gctx = ctx->cipher_data; /* If not set up, return error */ if (!gctx->key_set) return -1; if (gctx->tls_aad_len >= 0) return aes_gcm_tls_cipher(ctx, out, in, len); if (!gctx->iv_set) return -1; if (in) { if (out == NULL) { if (CRYPTO_gcm128_aad(&gctx->gcm, in, len)) return -1; } else if (ctx->encrypt) { if (gctx->ctr) { if (CRYPTO_gcm128_encrypt_ctr32(&gctx->gcm, in, out, len, gctx->ctr)) return -1; } else { if (CRYPTO_gcm128_encrypt(&gctx->gcm, in, out, len)) return -1; } } else { if (gctx->ctr) { if (CRYPTO_gcm128_decrypt_ctr32(&gctx->gcm, in, out, len, gctx->ctr)) return -1; } else { if (CRYPTO_gcm128_decrypt(&gctx->gcm, in, out, len)) return -1; } } return len; } else { if (!ctx->encrypt) { if (gctx->taglen < 0) return -1; if (CRYPTO_gcm128_finish(&gctx->gcm, ctx->buf, gctx->taglen) != 0) return -1; gctx->iv_set = 0; return 0; } CRYPTO_gcm128_tag(&gctx->gcm, ctx->buf, 16); gctx->taglen = 16; /* Don't reuse the IV */ gctx->iv_set = 0; return 0; } }
static int aead_aes_gcm_open(const EVP_AEAD_CTX *ctx, uint8_t *out, size_t *out_len, size_t max_out_len, const uint8_t *nonce, size_t nonce_len, const uint8_t *in, size_t in_len, const uint8_t *ad, size_t ad_len) { size_t bulk = 0; const struct aead_aes_gcm_ctx *gcm_ctx = ctx->aead_state; uint8_t tag[EVP_AEAD_AES_GCM_TAG_LEN]; size_t plaintext_len; GCM128_CONTEXT gcm; if (in_len < gcm_ctx->tag_len) { OPENSSL_PUT_ERROR(CIPHER, aead_aes_gcm_open, CIPHER_R_BAD_DECRYPT); return 0; } plaintext_len = in_len - gcm_ctx->tag_len; if (max_out_len < plaintext_len) { OPENSSL_PUT_ERROR(CIPHER, aead_aes_gcm_open, CIPHER_R_BUFFER_TOO_SMALL); return 0; } memcpy(&gcm, &gcm_ctx->gcm, sizeof(gcm)); CRYPTO_gcm128_setiv(&gcm, nonce, nonce_len); if (!CRYPTO_gcm128_aad(&gcm, ad, ad_len)) { return 0; } if (gcm_ctx->ctr) { if (!CRYPTO_gcm128_decrypt_ctr32(&gcm, in + bulk, out + bulk, in_len - bulk - gcm_ctx->tag_len, gcm_ctx->ctr)) { return 0; } } else { if (!CRYPTO_gcm128_decrypt(&gcm, in + bulk, out + bulk, in_len - bulk - gcm_ctx->tag_len)) { return 0; } } CRYPTO_gcm128_tag(&gcm, tag, gcm_ctx->tag_len); if (CRYPTO_memcmp(tag, in + plaintext_len, gcm_ctx->tag_len) != 0) { OPENSSL_PUT_ERROR(CIPHER, aead_aes_gcm_open, CIPHER_R_BAD_DECRYPT); return 0; } *out_len = plaintext_len; return 1; }
int evp_aead_aes_gcm_open(const void *ctx_buf, uint8_t *out, size_t *out_len, size_t max_out_len, const uint8_t *nonce, const uint8_t *in, size_t in_len, const uint8_t *ad, size_t ad_len) { aead_assert_open_seal_preconditions(alignof(struct aead_aes_gcm_ctx), ctx_buf, out, out_len, nonce, in, in_len, ad, ad_len); const struct aead_aes_gcm_ctx *gcm_ctx = ctx_buf; if (!aead_open_out_max_out_in_tag_len(out_len, max_out_len, in_len, EVP_AEAD_AES_GCM_TAG_LEN)) { /* |aead_open_out_max_out_in_tag_len| already called |OPENSSL_PUT_ERROR|. */ return 0; } uint8_t tag[EVP_AEAD_AES_GCM_TAG_LEN]; size_t plaintext_len; GCM128_CONTEXT gcm; plaintext_len = in_len - EVP_AEAD_AES_GCM_TAG_LEN; const AES_KEY *key = &gcm_ctx->ks.ks; memcpy(&gcm, &gcm_ctx->gcm, sizeof(gcm)); CRYPTO_gcm128_set_96_bit_iv(&gcm, key, nonce); if (!CRYPTO_gcm128_aad(&gcm, ad, ad_len)) { return 0; } if (gcm_ctx->ctr) { if (!CRYPTO_gcm128_decrypt_ctr32(&gcm, key, in, out, in_len - EVP_AEAD_AES_GCM_TAG_LEN, gcm_ctx->ctr)) { return 0; } } else { if (!CRYPTO_gcm128_decrypt(&gcm, key, in, out, in_len - EVP_AEAD_AES_GCM_TAG_LEN)) { return 0; } } CRYPTO_gcm128_tag(&gcm, tag, EVP_AEAD_AES_GCM_TAG_LEN); if (CRYPTO_memcmp(tag, in + plaintext_len, EVP_AEAD_AES_GCM_TAG_LEN) != 0) { OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BAD_DECRYPT); return 0; } return 1; }
static int aead_aes_gcm_open(const EVP_AEAD_CTX *ctx, unsigned char *out, size_t *out_len, size_t max_out_len, const unsigned char *nonce, size_t nonce_len, const unsigned char *in, size_t in_len, const unsigned char *ad, size_t ad_len) { const struct aead_aes_gcm_ctx *gcm_ctx = ctx->aead_state; unsigned char tag[EVP_AEAD_AES_GCM_TAG_LEN]; GCM128_CONTEXT gcm; size_t plaintext_len; size_t bulk = 0; if (in_len < gcm_ctx->tag_len) { EVPerr(EVP_F_AEAD_AES_GCM_OPEN, EVP_R_BAD_DECRYPT); return 0; } plaintext_len = in_len - gcm_ctx->tag_len; if (max_out_len < plaintext_len) { EVPerr(EVP_F_AEAD_AES_GCM_OPEN, EVP_R_BUFFER_TOO_SMALL); return 0; } memcpy(&gcm, &gcm_ctx->gcm, sizeof(gcm)); CRYPTO_gcm128_setiv(&gcm, nonce, nonce_len); if (CRYPTO_gcm128_aad(&gcm, ad, ad_len)) return 0; if (gcm_ctx->ctr) { if (CRYPTO_gcm128_decrypt_ctr32(&gcm, in + bulk, out + bulk, in_len - bulk - gcm_ctx->tag_len, gcm_ctx->ctr)) return 0; } else { if (CRYPTO_gcm128_decrypt(&gcm, in + bulk, out + bulk, in_len - bulk - gcm_ctx->tag_len)) return 0; } CRYPTO_gcm128_tag(&gcm, tag, gcm_ctx->tag_len); if (CRYPTO_memcmp(tag, in + plaintext_len, gcm_ctx->tag_len) != 0) { EVPerr(EVP_F_AEAD_AES_GCM_OPEN, EVP_R_BAD_DECRYPT); return 0; } *out_len = plaintext_len; return 1; }
static int aead_aes_gcm_open_gather(const EVP_AEAD_CTX *ctx, uint8_t *out, const uint8_t *nonce, size_t nonce_len, const uint8_t *in, size_t in_len, const uint8_t *in_tag, size_t in_tag_len, const uint8_t *ad, size_t ad_len) { const struct aead_aes_gcm_ctx *gcm_ctx = ctx->aead_state; uint8_t tag[EVP_AEAD_AES_GCM_TAG_LEN]; if (nonce_len == 0) { OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_INVALID_NONCE_SIZE); return 0; } if (in_tag_len != ctx->tag_len) { OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BAD_DECRYPT); return 0; } const AES_KEY *key = &gcm_ctx->ks.ks; GCM128_CONTEXT gcm; OPENSSL_memset(&gcm, 0, sizeof(gcm)); OPENSSL_memcpy(&gcm.gcm_key, &gcm_ctx->gcm_key, sizeof(gcm.gcm_key)); CRYPTO_gcm128_setiv(&gcm, key, nonce, nonce_len); if (!CRYPTO_gcm128_aad(&gcm, ad, ad_len)) { return 0; } if (gcm_ctx->ctr) { if (!CRYPTO_gcm128_decrypt_ctr32(&gcm, key, in, out, in_len, gcm_ctx->ctr)) { return 0; } } else { if (!CRYPTO_gcm128_decrypt(&gcm, key, in, out, in_len)) { return 0; } } CRYPTO_gcm128_tag(&gcm, tag, ctx->tag_len); if (CRYPTO_memcmp(tag, in_tag, ctx->tag_len) != 0) { OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BAD_DECRYPT); return 0; } return 1; }
static int test_gcm128(int idx) { unsigned char out[512]; SIZED_DATA K = gcm128_vectors[idx].K; SIZED_DATA IV = gcm128_vectors[idx].IV; SIZED_DATA A = gcm128_vectors[idx].A; SIZED_DATA P = gcm128_vectors[idx].P; SIZED_DATA C = gcm128_vectors[idx].C; SIZED_DATA T = gcm128_vectors[idx].T; GCM128_CONTEXT ctx; AES_KEY key; /* Size 1 inputs are special-cased to signal NULL. */ if (A.size == 1) A.data = NULL; if (P.size == 1) P.data = NULL; if (C.size == 1) C.data = NULL; AES_set_encrypt_key(K.data, K.size * 8, &key); CRYPTO_gcm128_init(&ctx, &key, (block128_f)AES_encrypt); CRYPTO_gcm128_setiv(&ctx, IV.data, IV.size); memset(out, 0, P.size); if (A.data != NULL) CRYPTO_gcm128_aad(&ctx, A.data, A.size); if (P.data != NULL) CRYPTO_gcm128_encrypt( &ctx, P.data, out, P.size); if (!TEST_false(CRYPTO_gcm128_finish(&ctx, T.data, 16)) || (C.data != NULL && !TEST_mem_eq(out, P.size, C.data, P.size))) return 0; CRYPTO_gcm128_setiv(&ctx, IV.data, IV.size); memset(out, 0, P.size); if (A.data != NULL) CRYPTO_gcm128_aad(&ctx, A.data, A.size); if (C.data != NULL) CRYPTO_gcm128_decrypt(&ctx, C.data, out, P.size); if (!TEST_false(CRYPTO_gcm128_finish(&ctx, T.data, 16)) || (P.data != NULL && !TEST_mem_eq(out, P.size, P.data, P.size))) return 0; return 1; }
static int aria_gcm_cipher(EVP_CIPHER_CTX *ctx, unsigned char *out, const unsigned char *in, size_t len) { EVP_ARIA_GCM_CTX *gctx = EVP_C_DATA(EVP_ARIA_GCM_CTX,ctx); /* If not set up, return error */ if (!gctx->key_set) return -1; if (gctx->tls_aad_len >= 0) return aria_gcm_tls_cipher(ctx, out, in, len); if (!gctx->iv_set) return -1; if (in) { if (out == NULL) { if (CRYPTO_gcm128_aad(&gctx->gcm, in, len)) return -1; } else if (EVP_CIPHER_CTX_encrypting(ctx)) { if (CRYPTO_gcm128_encrypt(&gctx->gcm, in, out, len)) return -1; } else { if (CRYPTO_gcm128_decrypt(&gctx->gcm, in, out, len)) return -1; } return len; } if (!EVP_CIPHER_CTX_encrypting(ctx)) { if (gctx->taglen < 0) return -1; if (CRYPTO_gcm128_finish(&gctx->gcm, EVP_CIPHER_CTX_buf_noconst(ctx), gctx->taglen) != 0) return -1; gctx->iv_set = 0; return 0; } CRYPTO_gcm128_tag(&gctx->gcm, EVP_CIPHER_CTX_buf_noconst(ctx), 16); gctx->taglen = 16; /* Don't reuse the IV */ gctx->iv_set = 0; return 0; }
static int run_test_case(unsigned test_num, const struct test_case *test) { size_t key_len, plaintext_len, additional_data_len, nonce_len, ciphertext_len, tag_len; uint8_t *key = NULL, *plaintext = NULL, *additional_data = NULL, *nonce = NULL, *ciphertext = NULL, *tag = NULL, *out = NULL; int ret = 0; AES_KEY aes_key; GCM128_CONTEXT ctx; if (!decode_hex(&key, &key_len, test->key, test_num, "key") || !decode_hex(&plaintext, &plaintext_len, test->plaintext, test_num, "plaintext") || !decode_hex(&additional_data, &additional_data_len, test->additional_data, test_num, "additional_data") || !decode_hex(&nonce, &nonce_len, test->nonce, test_num, "nonce") || !decode_hex(&ciphertext, &ciphertext_len, test->ciphertext, test_num, "ciphertext") || !decode_hex(&tag, &tag_len, test->tag, test_num, "tag")) { goto out; } if (plaintext_len != ciphertext_len) { fprintf(stderr, "%u: plaintext and ciphertext have differing lengths.\n", test_num); goto out; } if (key_len != 16 && key_len != 24 && key_len != 32) { fprintf(stderr, "%u: bad key length.\n", test_num); goto out; } if (tag_len != 16) { fprintf(stderr, "%u: bad tag length.\n", test_num); goto out; } out = OPENSSL_malloc(plaintext_len); if (AES_set_encrypt_key(key, key_len*8, &aes_key)) { fprintf(stderr, "%u: AES_set_encrypt_key failed.\n", test_num); goto out; } CRYPTO_gcm128_init(&ctx, &aes_key, (block128_f) AES_encrypt); CRYPTO_gcm128_setiv(&ctx, nonce, nonce_len); memset(out, 0, plaintext_len); if (additional_data) { CRYPTO_gcm128_aad(&ctx, additional_data, additional_data_len); } if (plaintext) { CRYPTO_gcm128_encrypt(&ctx, plaintext, out, plaintext_len); } if (!CRYPTO_gcm128_finish(&ctx, tag, tag_len) || (ciphertext && memcmp(out, ciphertext, plaintext_len) != 0)) { fprintf(stderr, "%u: encrypt failed.\n", test_num); hexdump("got ", out, plaintext_len); hexdump("want", ciphertext, plaintext_len); goto out; } CRYPTO_gcm128_setiv(&ctx, nonce, nonce_len); memset(out, 0, plaintext_len); if (additional_data) { CRYPTO_gcm128_aad(&ctx, additional_data, additional_data_len); } if (ciphertext) { CRYPTO_gcm128_decrypt(&ctx, ciphertext, out, plaintext_len); } if (!CRYPTO_gcm128_finish(&ctx, tag, tag_len)) { fprintf(stderr, "%u: decrypt failed.\n", test_num); goto out; } if (plaintext && memcmp(out, plaintext, plaintext_len)) { fprintf(stderr, "%u: plaintext doesn't match.\n", test_num); goto out; } ret = 1; out: OPENSSL_free(key); OPENSSL_free(plaintext); OPENSSL_free(additional_data); OPENSSL_free(nonce); OPENSSL_free(ciphertext); OPENSSL_free(tag); OPENSSL_free(out); return ret; }
static int aes_gcm_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out, const uint8_t *in, size_t len) { EVP_AES_GCM_CTX *gctx = ctx->cipher_data; /* If not set up, return error */ if (!gctx->key_set) { return -1; } if (!gctx->iv_set) { return -1; } if (in) { if (out == NULL) { if (!CRYPTO_gcm128_aad(&gctx->gcm, in, len)) { return -1; } } else if (ctx->encrypt) { if (gctx->ctr) { size_t bulk = 0; #if defined(AES_GCM_ASM) if (len >= 32 && AES_GCM_ASM(gctx)) { size_t res = (16 - gctx->gcm.mres) % 16; if (!CRYPTO_gcm128_encrypt(&gctx->gcm, in, out, res)) { return -1; } bulk = AES_gcm_encrypt(in + res, out + res, len - res, gctx->gcm.key, gctx->gcm.Yi.c, gctx->gcm.Xi.u); gctx->gcm.len.u[1] += bulk; bulk += res; } #endif if (!CRYPTO_gcm128_encrypt_ctr32(&gctx->gcm, in + bulk, out + bulk, len - bulk, gctx->ctr)) { return -1; } } else { size_t bulk = 0; if (!CRYPTO_gcm128_encrypt(&gctx->gcm, in + bulk, out + bulk, len - bulk)) { return -1; } } } else { if (gctx->ctr) { size_t bulk = 0; #if defined(AES_GCM_ASM) if (len >= 16 && AES_GCM_ASM(gctx)) { size_t res = (16 - gctx->gcm.mres) % 16; if (!CRYPTO_gcm128_decrypt(&gctx->gcm, in, out, res)) { return -1; } bulk = AES_gcm_decrypt(in + res, out + res, len - res, gctx->gcm.key, gctx->gcm.Yi.c, gctx->gcm.Xi.u); gctx->gcm.len.u[1] += bulk; bulk += res; } #endif if (!CRYPTO_gcm128_decrypt_ctr32(&gctx->gcm, in + bulk, out + bulk, len - bulk, gctx->ctr)) { return -1; } } else { size_t bulk = 0; if (!CRYPTO_gcm128_decrypt(&gctx->gcm, in + bulk, out + bulk, len - bulk)) { return -1; } } } return len; } else { if (!ctx->encrypt) { if (gctx->taglen < 0 || !CRYPTO_gcm128_finish(&gctx->gcm, ctx->buf, gctx->taglen) != 0) { return -1; } gctx->iv_set = 0; return 0; } CRYPTO_gcm128_tag(&gctx->gcm, ctx->buf, 16); gctx->taglen = 16; /* Don't reuse the IV */ gctx->iv_set = 0; return 0; } }
static int aes_gcm_tls_cipher(EVP_CIPHER_CTX *ctx, unsigned char *out, const unsigned char *in, size_t len) { EVP_AES_GCM_CTX *gctx = ctx->cipher_data; int rv = -1; /* Encrypt/decrypt must be performed in place */ if (out != in || len < (EVP_GCM_TLS_EXPLICIT_IV_LEN + EVP_GCM_TLS_TAG_LEN)) return -1; /* Set IV from start of buffer or generate IV and write to start * of buffer. */ if (EVP_CIPHER_CTX_ctrl(ctx, ctx->encrypt ? EVP_CTRL_GCM_IV_GEN : EVP_CTRL_GCM_SET_IV_INV, EVP_GCM_TLS_EXPLICIT_IV_LEN, out) <= 0) goto err; /* Use saved AAD */ if (CRYPTO_gcm128_aad(&gctx->gcm, ctx->buf, gctx->tls_aad_len)) goto err; /* Fix buffer and length to point to payload */ in += EVP_GCM_TLS_EXPLICIT_IV_LEN; out += EVP_GCM_TLS_EXPLICIT_IV_LEN; len -= EVP_GCM_TLS_EXPLICIT_IV_LEN + EVP_GCM_TLS_TAG_LEN; if (ctx->encrypt) { /* Encrypt payload */ if (gctx->ctr) { if (CRYPTO_gcm128_encrypt_ctr32(&gctx->gcm, in, out, len, gctx->ctr)) goto err; } else { if (CRYPTO_gcm128_encrypt(&gctx->gcm, in, out, len)) goto err; } out += len; /* Finally write tag */ CRYPTO_gcm128_tag(&gctx->gcm, out, EVP_GCM_TLS_TAG_LEN); rv = len + EVP_GCM_TLS_EXPLICIT_IV_LEN + EVP_GCM_TLS_TAG_LEN; } else { /* Decrypt */ if (gctx->ctr) { if (CRYPTO_gcm128_decrypt_ctr32(&gctx->gcm, in, out, len, gctx->ctr)) goto err; } else { if (CRYPTO_gcm128_decrypt(&gctx->gcm, in, out, len)) goto err; } /* Retrieve tag */ CRYPTO_gcm128_tag(&gctx->gcm, ctx->buf, EVP_GCM_TLS_TAG_LEN); /* If tag mismatch wipe buffer */ if (memcmp(ctx->buf, in + len, EVP_GCM_TLS_TAG_LEN)) { explicit_bzero(out, len); goto err; } rv = len; } err: gctx->iv_set = 0; gctx->tls_aad_len = -1; return rv; }