| /* |
| * |
| * Embedded Linux library |
| * |
| * Copyright (C) 2016 Intel Corporation. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library 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 |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #define _GNU_SOURCE |
| #include <unistd.h> |
| #include <stdint.h> |
| #include <sys/syscall.h> |
| #include <linux/keyctl.h> |
| #include <errno.h> |
| |
| #include "private.h" |
| #include "useful.h" |
| #include "key.h" |
| #include "string.h" |
| #include "random.h" |
| #include "missing.h" |
| |
| #ifndef KEYCTL_DH_COMPUTE |
| #define KEYCTL_DH_COMPUTE 23 |
| #endif |
| |
| #ifndef KEYCTL_PKEY_QUERY |
| #define KEYCTL_PKEY_QUERY 24 |
| #define KEYCTL_PKEY_ENCRYPT 25 |
| #define KEYCTL_PKEY_DECRYPT 26 |
| #define KEYCTL_PKEY_SIGN 27 |
| #define KEYCTL_PKEY_VERIFY 28 |
| |
| #define KEYCTL_SUPPORTS_ENCRYPT 0x01 |
| #define KEYCTL_SUPPORTS_DECRYPT 0x02 |
| #define KEYCTL_SUPPORTS_SIGN 0x04 |
| #define KEYCTL_SUPPORTS_VERIFY 0x08 |
| |
| struct keyctl_pkey_query { |
| uint32_t supported_ops; |
| uint32_t key_size; |
| uint16_t max_data_size; |
| uint16_t max_sig_size; |
| uint16_t max_enc_size; |
| uint16_t max_dec_size; |
| |
| uint32_t __spare[10]; |
| }; |
| |
| struct keyctl_pkey_params { |
| int32_t key_id; |
| uint32_t in_len; |
| union { |
| uint32_t out_len; |
| uint32_t in2_len; |
| }; |
| uint32_t __spare[7]; |
| }; |
| |
| /* Work around the missing (pre-4.7) or broken (4.14.{70,71,72} and |
| * 4.18.{8,9,10}) kernel declaration of struct keyctl_dh_params |
| */ |
| struct dh_params { |
| int32_t private; |
| int32_t prime; |
| int32_t base; |
| }; |
| #else |
| /* When KEYCTL_PKEY_QUERY is defined by the kernel, the |
| * struct keyctl_dh_params declaration is valid. |
| */ |
| #define dh_params keyctl_dh_params |
| #endif |
| |
| #ifndef KEYCTL_RESTRICT_KEYRING |
| #define KEYCTL_RESTRICT_KEYRING 29 |
| #endif |
| |
| static int32_t internal_keyring; |
| |
| struct l_key { |
| int type; |
| int32_t serial; |
| }; |
| |
| struct l_keyring { |
| int32_t serial; |
| }; |
| |
| static const char * const key_type_names[] = { |
| [L_KEY_RAW] = "user", |
| [L_KEY_RSA] = "asymmetric", |
| }; |
| |
| static long kernel_add_key(const char *type, const char *description, |
| const void *payload, size_t len, int32_t keyring) |
| { |
| long result; |
| |
| result = syscall(__NR_add_key, type, description, payload, len, |
| keyring); |
| |
| return result >= 0 ? result : -errno; |
| } |
| |
| static long kernel_read_key(int32_t serial, const void *payload, size_t len) |
| { |
| long result; |
| |
| result = syscall(__NR_keyctl, KEYCTL_READ, serial, payload, len); |
| |
| return result >= 0 ? result : -errno; |
| } |
| |
| static long kernel_update_key(int32_t serial, const void *payload, size_t len) |
| { |
| long result; |
| |
| result = syscall(__NR_keyctl, KEYCTL_UPDATE, serial, payload, len); |
| |
| return result >= 0 ? result : -errno; |
| } |
| |
| static long kernel_invalidate_key(int32_t serial) |
| { |
| long result; |
| |
| result = syscall(__NR_keyctl, KEYCTL_INVALIDATE, serial); |
| |
| return result >= 0 ? result : -errno; |
| } |
| |
| static long kernel_link_key(int32_t key_serial, int32_t ring_serial) |
| { |
| long result; |
| |
| result = syscall(__NR_keyctl, KEYCTL_LINK, key_serial, ring_serial); |
| |
| return result >= 0 ? result : -errno; |
| } |
| |
| static long kernel_unlink_key(int32_t key_serial, int32_t ring_serial) |
| { |
| long result; |
| |
| result = syscall(__NR_keyctl, KEYCTL_UNLINK, key_serial, ring_serial); |
| |
| return result >= 0 ? result : -errno; |
| } |
| |
| static char *format_key_info(const char *encoding, const char *hash) |
| { |
| struct l_string *info; |
| |
| if (!encoding && !hash) |
| return NULL; |
| |
| info = l_string_new(0); |
| |
| if (encoding) |
| l_string_append_printf(info, "enc=%s ", encoding); |
| |
| if (hash) |
| l_string_append_printf(info, "hash=%s", hash); |
| |
| return l_string_unwrap(info); |
| } |
| |
| static long kernel_query_key(int32_t key_serial, const char *encoding, |
| const char *hash, size_t *size, bool *public) |
| { |
| long result; |
| struct keyctl_pkey_query query; |
| char *info = format_key_info(encoding, hash); |
| |
| memset(&query, 0, sizeof(query)); |
| |
| result = syscall(__NR_keyctl, KEYCTL_PKEY_QUERY, key_serial, 0, |
| info ?: "", &query); |
| if (result == 0) { |
| *size = query.key_size; |
| *public = ((query.supported_ops & KEYCTL_SUPPORTS_ENCRYPT) && |
| !(query.supported_ops & KEYCTL_SUPPORTS_DECRYPT)); |
| } |
| l_free(info); |
| |
| return result >= 0 ? result : -errno; |
| } |
| |
| static long kernel_dh_compute(int32_t private, int32_t prime, int32_t base, |
| void *payload, size_t len) |
| { |
| long result; |
| |
| struct dh_params params = { .private = private, |
| .prime = prime, |
| .base = base }; |
| |
| result = syscall(__NR_keyctl, KEYCTL_DH_COMPUTE, ¶ms, payload, len, |
| NULL); |
| |
| return result >= 0 ? result : -errno; |
| } |
| |
| static long kernel_restrict_keyring(int32_t serial, const char *keytype, |
| const char *restriction) |
| { |
| long result; |
| |
| result = syscall(__NR_keyctl, KEYCTL_RESTRICT_KEYRING, serial, keytype, |
| restriction); |
| |
| return result >= 0 ? result : -errno; |
| } |
| |
| static long kernel_key_eds(int op, int32_t serial, const char *encoding, |
| const char *hash, const void *in, void *out, |
| size_t len_in, size_t len_out) |
| { |
| long result; |
| struct keyctl_pkey_params params = { .key_id = serial, |
| .in_len = len_in, |
| .out_len = len_out }; |
| char *info = format_key_info(encoding, hash); |
| |
| memset(out, 0, len_out); |
| |
| result = syscall(__NR_keyctl, op, ¶ms, info ?: "", in, out); |
| l_free(info); |
| |
| return result >= 0 ? result : -errno; |
| } |
| |
| static long kernel_key_verify(int32_t serial, |
| const char *encoding, const char *hash, |
| const void *data, size_t data_len, |
| const void *sig, size_t sig_len) |
| { |
| struct keyctl_pkey_params params = { |
| .key_id = serial, |
| .in_len = data_len, |
| .in2_len = sig_len, |
| }; |
| char *info = format_key_info(encoding, hash); |
| long result; |
| |
| result = syscall(__NR_keyctl, KEYCTL_PKEY_VERIFY, ¶ms, |
| info ?: "", data, sig); |
| l_free(info); |
| |
| return result >= 0 ? result : -errno; |
| } |
| |
| static bool setup_internal_keyring(void) |
| { |
| internal_keyring = kernel_add_key("keyring", "ell-internal", NULL, 0, |
| KEY_SPEC_THREAD_KEYRING); |
| |
| if (internal_keyring <= 0) { |
| internal_keyring = 0; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| LIB_EXPORT struct l_key *l_key_new(enum l_key_type type, const void *payload, |
| size_t payload_length) |
| { |
| struct l_key *key; |
| char *description; |
| static unsigned long key_idx; |
| |
| if (unlikely(!payload)) |
| return NULL; |
| |
| if (unlikely((size_t)type >= L_ARRAY_SIZE(key_type_names))) |
| return NULL; |
| |
| if (!internal_keyring && !setup_internal_keyring()) |
| return NULL; |
| |
| key = l_new(struct l_key, 1); |
| key->type = type; |
| description = l_strdup_printf("ell-key-%lu", key_idx++); |
| key->serial = kernel_add_key(key_type_names[type], description, payload, |
| payload_length, internal_keyring); |
| l_free(description); |
| |
| if (key->serial < 0) { |
| l_free(key); |
| key = NULL; |
| } |
| |
| /* |
| * TODO: Query asymmetric key algorithm from the kernel and |
| * ensure that it matches the expected l_key_type. This can |
| * currently be found by digging through /proc/keys, but a |
| * keyctl() op makes more sense. |
| */ |
| |
| return key; |
| } |
| |
| LIB_EXPORT void l_key_free(struct l_key *key) |
| { |
| if (unlikely(!key)) |
| return; |
| |
| /* |
| * Use invalidate as, unlike revoke, this doesn't delay the |
| * key garbage collection and causes the quota used by the |
| * key to be released sooner and more predictably. |
| */ |
| kernel_invalidate_key(key->serial); |
| |
| l_free(key); |
| } |
| |
| LIB_EXPORT void l_key_free_norevoke(struct l_key *key) |
| { |
| if (unlikely(!key)) |
| return; |
| |
| kernel_unlink_key(key->serial, internal_keyring); |
| |
| l_free(key); |
| } |
| |
| LIB_EXPORT bool l_key_update(struct l_key *key, const void *payload, size_t len) |
| { |
| long error; |
| |
| if (unlikely(!key)) |
| return false; |
| |
| error = kernel_update_key(key->serial, payload, len); |
| |
| return error == 0; |
| } |
| |
| LIB_EXPORT bool l_key_extract(struct l_key *key, void *payload, size_t *len) |
| { |
| long keylen; |
| |
| if (unlikely(!key)) |
| return false; |
| |
| keylen = kernel_read_key(key->serial, payload, *len); |
| |
| if (keylen < 0 || (size_t)keylen > *len) { |
| explicit_bzero(payload, *len); |
| return false; |
| } |
| |
| *len = keylen; |
| return true; |
| } |
| |
| LIB_EXPORT ssize_t l_key_get_payload_size(struct l_key *key) |
| { |
| return kernel_read_key(key->serial, NULL, 0); |
| } |
| |
| static const char *lookup_cipher(enum l_key_cipher_type cipher) |
| { |
| const char* ret = NULL; |
| |
| switch (cipher) { |
| case L_KEY_RSA_PKCS1_V1_5: |
| ret = "pkcs1"; |
| break; |
| case L_KEY_RSA_RAW: |
| ret = "raw"; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static const char *lookup_checksum(enum l_checksum_type checksum) |
| { |
| const char* ret = NULL; |
| |
| switch (checksum) { |
| case L_CHECKSUM_NONE: |
| break; |
| case L_CHECKSUM_MD4: |
| ret = "md4"; |
| break; |
| case L_CHECKSUM_MD5: |
| ret = "md5"; |
| break; |
| case L_CHECKSUM_SHA1: |
| ret = "sha1"; |
| break; |
| case L_CHECKSUM_SHA224: |
| ret = "sha224"; |
| break; |
| case L_CHECKSUM_SHA256: |
| ret = "sha256"; |
| break; |
| case L_CHECKSUM_SHA384: |
| ret = "sha384"; |
| break; |
| case L_CHECKSUM_SHA512: |
| ret = "sha512"; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| LIB_EXPORT bool l_key_get_info(struct l_key *key, enum l_key_cipher_type cipher, |
| enum l_checksum_type checksum, size_t *bits, |
| bool *public) |
| { |
| if (unlikely(!key)) |
| return false; |
| |
| return !kernel_query_key(key->serial, lookup_cipher(cipher), |
| lookup_checksum(checksum), bits, |
| public); |
| } |
| |
| LIB_EXPORT struct l_key *l_key_generate_dh_private(const void *prime_buf, |
| size_t prime_len) |
| { |
| uint8_t *buf; |
| const uint8_t *prime = prime_buf; |
| size_t prime_bits; |
| unsigned int i; |
| size_t private_bytes; |
| size_t random_bytes; |
| struct l_key *private; |
| |
| /* Find the prime's bit length excluding leading 0s */ |
| |
| for (i = 0; i < prime_len && !prime[i]; i++); |
| |
| if (i == prime_len || (i == prime_len - 1 && prime[i] < 5)) |
| return NULL; |
| |
| prime_bits = (prime_len - i) * 8 - __builtin_clz(prime[i]); |
| |
| /* |
| * Generate a random DH private value conforming to 1 < x < p - 1. |
| * To do this covering all possible values in this range with the |
| * same probability of generating each value generally requires |
| * looping. Instead we generate a value in the range |
| * [2 ^ (prime_bits - 2), 2 ^ (prime_bits - 1) - 1] by forcing bit |
| * prime_bits - 2 to 1, i.e. the range in PKCS #3 Section 7.1 for |
| * l equal to prime_bits - 1. This means we're using between |
| * one half and one quarter of the full [2, p - 2] range, i.e. |
| * between 1 and 2 bits fewer. Note that since p is odd |
| * p - 1 has the same bit length as p and so our maximum value |
| * 2 ^ (prime_bits - 1) - 1 is still less than p - 1. |
| */ |
| private_bytes = ((prime_bits - 1) + 7) / 8; |
| random_bytes = ((prime_bits - 2) + 7) / 8; |
| buf = l_malloc(private_bytes); |
| l_getrandom(buf + private_bytes - random_bytes, random_bytes); |
| |
| buf[0] &= (1 << ((prime_bits - 2) % 8)) - 1; |
| buf[0] |= 1 << ((prime_bits - 2) % 8); |
| |
| private = l_key_new(L_KEY_RAW, buf, private_bytes); |
| explicit_bzero(buf, private_bytes); |
| l_free(buf); |
| return private; |
| } |
| |
| static bool compute_common(struct l_key *base, struct l_key *private, |
| struct l_key *prime, void *payload, size_t *len) |
| { |
| long result_len; |
| bool usable_payload = *len != 0; |
| |
| result_len = kernel_dh_compute(private->serial, prime->serial, |
| base->serial, payload, *len); |
| |
| if (result_len > 0) { |
| *len = result_len; |
| return usable_payload; |
| } |
| return false; |
| } |
| |
| LIB_EXPORT bool l_key_compute_dh_public(struct l_key *generator, |
| struct l_key *private, |
| struct l_key *prime, |
| void *payload, size_t *len) |
| { |
| return compute_common(generator, private, prime, payload, len); |
| } |
| |
| LIB_EXPORT bool l_key_compute_dh_secret(struct l_key *other_public, |
| struct l_key *private, |
| struct l_key *prime, |
| void *payload, size_t *len) |
| { |
| return compute_common(other_public, private, prime, payload, len); |
| } |
| |
| static int be_bignum_compare(const uint8_t *a, size_t a_len, |
| const uint8_t *b, size_t b_len) |
| { |
| unsigned int i; |
| |
| if (a_len >= b_len) { |
| for (i = 0; i < a_len - b_len; i++) |
| if (a[i]) |
| return 1; |
| |
| return memcmp(a + i, b, b_len); |
| } |
| |
| for (i = 0; i < b_len - a_len; i++) |
| if (b[i]) |
| return -1; |
| |
| return memcmp(a, b + i, a_len); |
| } |
| |
| /* |
| * Validate that @payload is within range for a private and public key for |
| * a DH computation in the finite field group defined by modulus @prime_buf, |
| * both numbers stored as big-endian integers. We require a key in the |
| * [2, prime - 2] (inclusive) interval. PKCS #3 does not exclude 1 as a |
| * private key but other specs do. |
| */ |
| LIB_EXPORT bool l_key_validate_dh_payload(const void *payload, size_t len, |
| const void *prime_buf, size_t prime_len) |
| { |
| static const uint8_t one[] = { 1 }; |
| uint8_t prime_1[prime_len]; |
| |
| /* |
| * Produce prime - 1 for the payload < prime - 1 check. |
| * prime is odd so just zero the LSB. |
| */ |
| memcpy(prime_1, prime_buf, prime_len); |
| |
| if (prime_len < 1 || !(prime_1[prime_len - 1] & 1)) |
| return false; |
| |
| prime_1[prime_len - 1] &= ~1; |
| |
| if (be_bignum_compare(payload, len, one, 1) <= 0) |
| return false; |
| |
| if (be_bignum_compare(payload, len, prime_1, prime_len) >= 0) |
| return false; |
| |
| return true; |
| } |
| |
| /* Common code for encrypt/decrypt/sign */ |
| static ssize_t eds_common(struct l_key *key, |
| enum l_key_cipher_type cipher, |
| enum l_checksum_type checksum, const void *in, |
| void *out, size_t len_in, size_t len_out, |
| int op) |
| { |
| if (unlikely(!key)) |
| return -EINVAL; |
| |
| return kernel_key_eds(op, key->serial, lookup_cipher(cipher), |
| lookup_checksum(checksum), in, out, len_in, |
| len_out); |
| } |
| |
| LIB_EXPORT ssize_t l_key_encrypt(struct l_key *key, |
| enum l_key_cipher_type cipher, |
| enum l_checksum_type checksum, |
| const void *in, void *out, |
| size_t len_in, size_t len_out) |
| { |
| ssize_t ret_len; |
| |
| ret_len = eds_common(key, cipher, checksum, in, out, |
| len_in, len_out, |
| KEYCTL_PKEY_ENCRYPT); |
| |
| return ret_len; |
| } |
| |
| LIB_EXPORT ssize_t l_key_decrypt(struct l_key *key, |
| enum l_key_cipher_type cipher, |
| enum l_checksum_type checksum, |
| const void *in, void *out, |
| size_t len_in, size_t len_out) |
| { |
| ssize_t ret_len; |
| |
| ret_len = eds_common(key, cipher, checksum, in, out, len_in, |
| len_out, KEYCTL_PKEY_DECRYPT); |
| |
| if (ret_len < 0) |
| goto done; |
| |
| done: |
| return ret_len; |
| } |
| |
| LIB_EXPORT ssize_t l_key_sign(struct l_key *key, |
| enum l_key_cipher_type cipher, |
| enum l_checksum_type checksum, const void *in, |
| void *out, size_t len_in, size_t len_out) |
| { |
| ssize_t ret_len; |
| |
| ret_len = eds_common(key, cipher, checksum, in, out, |
| len_in, len_out, |
| KEYCTL_PKEY_SIGN); |
| |
| return ret_len; |
| } |
| |
| LIB_EXPORT bool l_key_verify(struct l_key *key, |
| enum l_key_cipher_type cipher, |
| enum l_checksum_type checksum, const void *data, |
| const void *sig, size_t len_data, |
| size_t len_sig) |
| { |
| long result; |
| |
| if (unlikely(!key)) |
| return false; |
| |
| result = kernel_key_verify(key->serial, lookup_cipher(cipher), |
| lookup_checksum(checksum), |
| data, len_data, |
| sig, len_sig); |
| |
| return result >= 0; |
| } |
| |
| LIB_EXPORT struct l_keyring *l_keyring_new(void) |
| { |
| struct l_keyring *keyring; |
| char *description; |
| static unsigned long keyring_idx; |
| |
| if (!internal_keyring && !setup_internal_keyring()) |
| return NULL; |
| |
| keyring = l_new(struct l_keyring, 1); |
| description = l_strdup_printf("ell-keyring-%lu", keyring_idx++); |
| keyring->serial = kernel_add_key("keyring", description, NULL, 0, |
| internal_keyring); |
| l_free(description); |
| |
| if (keyring->serial < 0) { |
| l_free(keyring); |
| return NULL; |
| } |
| |
| return keyring; |
| } |
| |
| LIB_EXPORT bool l_keyring_restrict(struct l_keyring *keyring, |
| enum l_keyring_restriction res, |
| const struct l_keyring *trusted) |
| { |
| char *restriction = NULL; |
| long result; |
| |
| switch (res) { |
| case L_KEYRING_RESTRICT_ASYM: |
| case L_KEYRING_RESTRICT_ASYM_CHAIN: |
| { |
| char *option = ""; |
| |
| if (res == L_KEYRING_RESTRICT_ASYM_CHAIN) |
| option = ":chain"; |
| |
| restriction = l_strdup_printf("key_or_keyring:%d%s", |
| trusted ? trusted->serial : 0, |
| option); |
| |
| break; |
| } |
| default: |
| /* Unsupported type */ |
| return NULL; |
| } |
| |
| result = kernel_restrict_keyring(keyring->serial, "asymmetric", |
| restriction); |
| |
| l_free(restriction); |
| |
| return result == 0; |
| } |
| |
| LIB_EXPORT void l_keyring_free(struct l_keyring *keyring) |
| { |
| if (unlikely(!keyring)) |
| return; |
| |
| kernel_invalidate_key(keyring->serial); |
| |
| l_free(keyring); |
| } |
| |
| LIB_EXPORT void l_keyring_free_norevoke(struct l_keyring *keyring) |
| { |
| if (unlikely(!keyring)) |
| return; |
| |
| kernel_unlink_key(keyring->serial, internal_keyring); |
| |
| l_free(keyring); |
| } |
| |
| LIB_EXPORT bool l_keyring_link(struct l_keyring *keyring, |
| const struct l_key *key) |
| { |
| long error; |
| |
| if (unlikely(!keyring) || unlikely(!key)) |
| return false; |
| |
| error = kernel_link_key(key->serial, keyring->serial); |
| |
| return error == 0; |
| } |
| |
| LIB_EXPORT bool l_keyring_unlink(struct l_keyring *keyring, |
| const struct l_key *key) |
| { |
| long error; |
| |
| if (unlikely(!keyring) || unlikely(!key)) |
| return false; |
| |
| error = kernel_unlink_key(key->serial, keyring->serial); |
| |
| return error == 0; |
| } |
| |
| LIB_EXPORT bool l_keyring_link_nested(struct l_keyring *keyring, |
| const struct l_keyring *nested) |
| { |
| long error; |
| |
| if (unlikely(!keyring) || unlikely(!nested)) |
| return false; |
| |
| error = kernel_link_key(nested->serial, keyring->serial); |
| |
| return error == 0; |
| } |
| |
| LIB_EXPORT bool l_keyring_unlink_nested(struct l_keyring *keyring, |
| const struct l_keyring *nested) |
| { |
| long error; |
| |
| if (unlikely(!keyring) || unlikely(!nested)) |
| return false; |
| |
| error = kernel_unlink_key(nested->serial, keyring->serial); |
| |
| return error == 0; |
| } |
| |
| LIB_EXPORT bool l_key_is_supported(uint32_t features) |
| { |
| long result; |
| |
| if (features & L_KEY_FEATURE_DH) { |
| result = syscall(__NR_keyctl, KEYCTL_DH_COMPUTE, NULL, "x", 1, |
| NULL); |
| |
| if (result == -1 && errno == EOPNOTSUPP) |
| return false; |
| } |
| |
| if (features & L_KEY_FEATURE_RESTRICT) { |
| result = syscall(__NR_keyctl, KEYCTL_RESTRICT_KEYRING, 0, |
| "asymmetric", ""); |
| |
| if (result == -1 && errno == EOPNOTSUPP) |
| return false; |
| } |
| |
| if (features & L_KEY_FEATURE_CRYPTO) { |
| result = syscall(__NR_keyctl, KEYCTL_PKEY_QUERY, 0, 0, "", 0); |
| |
| if (result == -1 && errno == EOPNOTSUPP) |
| return false; |
| } |
| |
| return true; |
| } |