Add a bunch of compatibility functions for PKCS#7.
The full library is a bit much, but this is enough to appease most of cryptography.io. Change-Id: I1bb0d83744c4550d5fe23c5c98cfd7e36b17fcc9 Reviewed-on: https://boringssl-review.googlesource.com/29365 Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: Adam Langley <agl@google.com> CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
This commit is contained in:
parent
eaf0a17db8
commit
3815720cf3
@ -113,9 +113,7 @@
|
||||
#include <openssl/dsa.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
/*
|
||||
* #include <openssl/pkcs7.h>
|
||||
*/
|
||||
#include <openssl/pkcs7.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
@ -127,6 +125,7 @@ IMPLEMENT_PEM_rw(X509_REQ, X509_REQ, PEM_STRING_X509_REQ, X509_REQ)
|
||||
|
||||
IMPLEMENT_PEM_write(X509_REQ_NEW, X509_REQ, PEM_STRING_X509_REQ_OLD, X509_REQ)
|
||||
IMPLEMENT_PEM_rw(X509_CRL, X509_CRL, PEM_STRING_X509_CRL, X509_CRL)
|
||||
IMPLEMENT_PEM_rw(PKCS7, PKCS7, PEM_STRING_PKCS7, PKCS7)
|
||||
|
||||
/*
|
||||
* We treat RSA or DSA private keys as a special case. For private keys we
|
||||
|
@ -493,7 +493,6 @@ static void TestCertRepase(const uint8_t *der_bytes, size_t der_len) {
|
||||
EXPECT_EQ(0u, CBS_len(&pkcs7));
|
||||
|
||||
ASSERT_EQ(sk_X509_num(certs.get()), sk_X509_num(certs2.get()));
|
||||
|
||||
for (size_t i = 0; i < sk_X509_num(certs.get()); i++) {
|
||||
X509 *a = sk_X509_value(certs.get(), i);
|
||||
X509 *b = sk_X509_value(certs2.get(), i);
|
||||
@ -506,6 +505,50 @@ static void TestCertRepase(const uint8_t *der_bytes, size_t der_len) {
|
||||
bssl::UniquePtr<uint8_t> free_result2_data(result2_data);
|
||||
|
||||
EXPECT_EQ(Bytes(result_data, result_len), Bytes(result2_data, result2_len));
|
||||
|
||||
// Parse with the legacy API instead.
|
||||
const uint8_t *ptr = der_bytes;
|
||||
bssl::UniquePtr<PKCS7> pkcs7_obj(d2i_PKCS7(nullptr, &ptr, der_len));
|
||||
ASSERT_TRUE(pkcs7_obj);
|
||||
EXPECT_EQ(ptr, der_bytes + der_len);
|
||||
|
||||
ASSERT_TRUE(PKCS7_type_is_signed(pkcs7_obj.get()));
|
||||
const STACK_OF(X509) *certs3 = pkcs7_obj->d.sign->cert;
|
||||
ASSERT_EQ(sk_X509_num(certs.get()), sk_X509_num(certs3));
|
||||
for (size_t i = 0; i < sk_X509_num(certs.get()); i++) {
|
||||
X509 *a = sk_X509_value(certs.get(), i);
|
||||
X509 *b = sk_X509_value(certs3, i);
|
||||
ASSERT_EQ(0, X509_cmp(a, b));
|
||||
}
|
||||
|
||||
// Serialize the original object. This should echo back the original saved
|
||||
// bytes.
|
||||
uint8_t *result3_data = nullptr;
|
||||
int result3_len = i2d_PKCS7(pkcs7_obj.get(), &result3_data);
|
||||
ASSERT_GT(result3_len, 0);
|
||||
bssl::UniquePtr<uint8_t> free_result3_data(result3_data);
|
||||
EXPECT_EQ(Bytes(der_bytes, der_len), Bytes(result3_data, result3_len));
|
||||
|
||||
// Make a new object with the legacy API.
|
||||
pkcs7_obj.reset(
|
||||
PKCS7_sign(nullptr, nullptr, certs.get(), nullptr, PKCS7_DETACHED));
|
||||
ASSERT_TRUE(pkcs7_obj);
|
||||
|
||||
ASSERT_TRUE(PKCS7_type_is_signed(pkcs7_obj.get()));
|
||||
const STACK_OF(X509) *certs4 = pkcs7_obj->d.sign->cert;
|
||||
ASSERT_EQ(sk_X509_num(certs.get()), sk_X509_num(certs4));
|
||||
for (size_t i = 0; i < sk_X509_num(certs.get()); i++) {
|
||||
X509 *a = sk_X509_value(certs.get(), i);
|
||||
X509 *b = sk_X509_value(certs4, i);
|
||||
ASSERT_EQ(0, X509_cmp(a, b));
|
||||
}
|
||||
|
||||
// This new object should serialize canonically.
|
||||
uint8_t *result4_data = nullptr;
|
||||
int result4_len = i2d_PKCS7(pkcs7_obj.get(), &result4_data);
|
||||
ASSERT_GT(result4_len, 0);
|
||||
bssl::UniquePtr<uint8_t> free_result4_data(result4_data);
|
||||
EXPECT_EQ(Bytes(result_data, result_len), Bytes(result4_data, result4_len));
|
||||
}
|
||||
|
||||
static void TestCRLReparse(const uint8_t *der_bytes, size_t der_len) {
|
||||
@ -532,7 +575,6 @@ static void TestCRLReparse(const uint8_t *der_bytes, size_t der_len) {
|
||||
EXPECT_EQ(0u, CBS_len(&pkcs7));
|
||||
|
||||
ASSERT_EQ(sk_X509_CRL_num(crls.get()), sk_X509_CRL_num(crls.get()));
|
||||
|
||||
for (size_t i = 0; i < sk_X509_CRL_num(crls.get()); i++) {
|
||||
X509_CRL *a = sk_X509_CRL_value(crls.get(), i);
|
||||
X509_CRL *b = sk_X509_CRL_value(crls2.get(), i);
|
||||
@ -545,6 +587,35 @@ static void TestCRLReparse(const uint8_t *der_bytes, size_t der_len) {
|
||||
bssl::UniquePtr<uint8_t> free_result2_data(result2_data);
|
||||
|
||||
EXPECT_EQ(Bytes(result_data, result_len), Bytes(result2_data, result2_len));
|
||||
|
||||
// Parse with the legacy API instead.
|
||||
const uint8_t *ptr = der_bytes;
|
||||
bssl::UniquePtr<PKCS7> pkcs7_obj(d2i_PKCS7(nullptr, &ptr, der_len));
|
||||
ASSERT_TRUE(pkcs7_obj);
|
||||
EXPECT_EQ(ptr, der_bytes + der_len);
|
||||
|
||||
ASSERT_TRUE(PKCS7_type_is_signed(pkcs7_obj.get()));
|
||||
const STACK_OF(X509_CRL) *crls3 = pkcs7_obj->d.sign->crl;
|
||||
ASSERT_EQ(sk_X509_CRL_num(crls.get()), sk_X509_CRL_num(crls3));
|
||||
for (size_t i = 0; i < sk_X509_CRL_num(crls.get()); i++) {
|
||||
X509_CRL *a = sk_X509_CRL_value(crls.get(), i);
|
||||
X509_CRL *b = sk_X509_CRL_value(crls3, i);
|
||||
ASSERT_EQ(0, X509_CRL_cmp(a, b));
|
||||
}
|
||||
|
||||
ptr = result_data;
|
||||
pkcs7_obj.reset(d2i_PKCS7(nullptr, &ptr, result_len));
|
||||
ASSERT_TRUE(pkcs7_obj);
|
||||
EXPECT_EQ(ptr, result_data + result_len);
|
||||
|
||||
ASSERT_TRUE(PKCS7_type_is_signed(pkcs7_obj.get()));
|
||||
const STACK_OF(X509_CRL) *crls4 = pkcs7_obj->d.sign->crl;
|
||||
ASSERT_EQ(sk_X509_CRL_num(crls.get()), sk_X509_CRL_num(crls4));
|
||||
for (size_t i = 0; i < sk_X509_CRL_num(crls.get()); i++) {
|
||||
X509_CRL *a = sk_X509_CRL_value(crls.get(), i);
|
||||
X509_CRL *b = sk_X509_CRL_value(crls4, i);
|
||||
ASSERT_EQ(0, X509_CRL_cmp(a, b));
|
||||
}
|
||||
}
|
||||
|
||||
static void TestPEMCerts(const char *pem) {
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include "internal.h"
|
||||
#include "../internal.h"
|
||||
|
||||
|
||||
int PKCS7_get_certificates(STACK_OF(X509) *out_certs, CBS *cbs) {
|
||||
@ -227,3 +228,168 @@ static int pkcs7_bundle_crls_cb(CBB *out, const void *arg) {
|
||||
int PKCS7_bundle_CRLs(CBB *out, const STACK_OF(X509_CRL) *crls) {
|
||||
return pkcs7_bundle(out, pkcs7_bundle_crls_cb, crls);
|
||||
}
|
||||
|
||||
static PKCS7 *pkcs7_new(CBS *cbs) {
|
||||
PKCS7 *ret = OPENSSL_malloc(sizeof(PKCS7));
|
||||
if (ret == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
OPENSSL_memset(ret, 0, sizeof(PKCS7));
|
||||
ret->type = (ASN1_OBJECT *)OBJ_nid2obj(NID_pkcs7_signed);
|
||||
ret->d.sign = OPENSSL_malloc(sizeof(PKCS7_SIGNED));
|
||||
if (ret->d.sign == NULL) {
|
||||
goto err;
|
||||
}
|
||||
ret->d.sign->cert = sk_X509_new_null();
|
||||
ret->d.sign->crl = sk_X509_CRL_new_null();
|
||||
CBS copy = *cbs, copy2 = *cbs;
|
||||
if (ret->d.sign->cert == NULL || ret->d.sign->crl == NULL ||
|
||||
!PKCS7_get_certificates(ret->d.sign->cert, ©) ||
|
||||
!PKCS7_get_CRLs(ret->d.sign->crl, cbs)) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (sk_X509_num(ret->d.sign->cert) == 0) {
|
||||
sk_X509_free(ret->d.sign->cert);
|
||||
ret->d.sign->cert = NULL;
|
||||
}
|
||||
|
||||
if (sk_X509_CRL_num(ret->d.sign->crl) == 0) {
|
||||
sk_X509_CRL_free(ret->d.sign->crl);
|
||||
ret->d.sign->crl = NULL;
|
||||
}
|
||||
|
||||
ret->ber_len = CBS_len(©2) - CBS_len(cbs);
|
||||
ret->ber_bytes = BUF_memdup(CBS_data(©2), ret->ber_len);
|
||||
if (ret->ber_bytes == NULL) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
err:
|
||||
PKCS7_free(ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PKCS7 *d2i_PKCS7(PKCS7 **out, const uint8_t **inp,
|
||||
size_t len) {
|
||||
CBS cbs;
|
||||
CBS_init(&cbs, *inp, len);
|
||||
PKCS7 *ret = pkcs7_new(&cbs);
|
||||
if (ret == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
*inp = CBS_data(&cbs);
|
||||
if (out != NULL) {
|
||||
PKCS7_free(*out);
|
||||
*out = ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
PKCS7 *d2i_PKCS7_bio(BIO *bio, PKCS7 **out) {
|
||||
// Use a generous bound, to allow for PKCS#7 files containing large root sets.
|
||||
static const size_t kMaxSize = 4 * 1024 * 1024;
|
||||
uint8_t *data;
|
||||
size_t len;
|
||||
if (!BIO_read_asn1(bio, &data, &len, kMaxSize)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
CBS cbs;
|
||||
CBS_init(&cbs, data, len);
|
||||
PKCS7 *ret = pkcs7_new(&cbs);
|
||||
OPENSSL_free(data);
|
||||
if (out != NULL && ret != NULL) {
|
||||
PKCS7_free(*out);
|
||||
*out = ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int i2d_PKCS7(const PKCS7 *p7, uint8_t **out) {
|
||||
if (p7->ber_len > INT_MAX) {
|
||||
OPENSSL_PUT_ERROR(PKCS8, ERR_R_OVERFLOW);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (out == NULL) {
|
||||
return (int)p7->ber_len;
|
||||
}
|
||||
|
||||
if (*out == NULL) {
|
||||
*out = OPENSSL_malloc(p7->ber_len);
|
||||
if (*out == NULL) {
|
||||
OPENSSL_PUT_ERROR(PKCS8, ERR_R_MALLOC_FAILURE);
|
||||
return -1;
|
||||
}
|
||||
OPENSSL_memcpy(*out, p7->ber_bytes, p7->ber_len);
|
||||
} else {
|
||||
OPENSSL_memcpy(*out, p7->ber_bytes, p7->ber_len);
|
||||
*out += p7->ber_len;
|
||||
}
|
||||
return (int)p7->ber_len;
|
||||
}
|
||||
|
||||
int i2d_PKCS7_bio(BIO *bio, const PKCS7 *p7) {
|
||||
size_t written = 0;
|
||||
while (written < p7->ber_len) {
|
||||
size_t todo = p7->ber_len - written;
|
||||
int len = todo > INT_MAX ? INT_MAX : (int)todo;
|
||||
int ret = BIO_write(bio, p7->ber_bytes + written, len);
|
||||
if (ret <= 0) {
|
||||
return 0;
|
||||
}
|
||||
written += (size_t)ret;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void PKCS7_free(PKCS7 *p7) {
|
||||
if (p7 == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
OPENSSL_free(p7->ber_bytes);
|
||||
ASN1_OBJECT_free(p7->type);
|
||||
// We only supported signed data.
|
||||
if (p7->d.sign != NULL) {
|
||||
sk_X509_pop_free(p7->d.sign->cert, X509_free);
|
||||
sk_X509_CRL_pop_free(p7->d.sign->crl, X509_CRL_free);
|
||||
OPENSSL_free(p7->d.sign);
|
||||
}
|
||||
OPENSSL_free(p7);
|
||||
}
|
||||
|
||||
// We only support signed data, so these getters are no-ops.
|
||||
int PKCS7_type_is_data(const PKCS7 *p7) { return 0; }
|
||||
int PKCS7_type_is_digest(const PKCS7 *p7) { return 0; }
|
||||
int PKCS7_type_is_encrypted(const PKCS7 *p7) { return 0; }
|
||||
int PKCS7_type_is_enveloped(const PKCS7 *p7) { return 0; }
|
||||
int PKCS7_type_is_signed(const PKCS7 *p7) { return 1; }
|
||||
int PKCS7_type_is_signedAndEnveloped(const PKCS7 *p7) { return 0; }
|
||||
|
||||
PKCS7 *PKCS7_sign(X509 *sign_cert, EVP_PKEY *pkey, STACK_OF(X509) *certs,
|
||||
BIO *data, int flags) {
|
||||
if (sign_cert != NULL || pkey != NULL || flags != PKCS7_DETACHED) {
|
||||
OPENSSL_PUT_ERROR(PKCS7, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t *der;
|
||||
size_t len;
|
||||
CBB cbb;
|
||||
if (!CBB_init(&cbb, 2048) ||
|
||||
!PKCS7_bundle_certificates(&cbb, certs) ||
|
||||
!CBB_finish(&cbb, &der, &len)) {
|
||||
CBB_cleanup(&cbb);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
CBS cbs;
|
||||
CBS_init(&cbs, der, len);
|
||||
PKCS7 *ret = pkcs7_new(&cbs);
|
||||
OPENSSL_free(der);
|
||||
return ret;
|
||||
}
|
||||
|
@ -63,6 +63,7 @@
|
||||
#include <openssl/digest.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/stack.h>
|
||||
#include <openssl/pkcs7.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
/* For compatibility with open-iscsi, which assumes that it can get
|
||||
@ -329,6 +330,7 @@ DECLARE_PEM_write(X509_REQ_NEW, X509_REQ)
|
||||
|
||||
DECLARE_PEM_rw(X509_CRL, X509_CRL)
|
||||
|
||||
DECLARE_PEM_rw(PKCS7, PKCS7)
|
||||
DECLARE_PEM_rw(PKCS8, X509_SIG)
|
||||
|
||||
DECLARE_PEM_rw(PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO)
|
||||
|
@ -82,8 +82,129 @@ OPENSSL_EXPORT int PKCS7_get_PEM_CRLs(STACK_OF(X509_CRL) *out_crls,
|
||||
BIO *pem_bio);
|
||||
|
||||
|
||||
// Deprecated functions.
|
||||
//
|
||||
// These functions are a compatibility layer over a subset of OpenSSL's PKCS#7
|
||||
// API. It intentionally does not implement the whole thing, only the minimum
|
||||
// needed to build cryptography.io.
|
||||
|
||||
typedef struct {
|
||||
STACK_OF(X509) *cert;
|
||||
STACK_OF(X509_CRL) *crl;
|
||||
} PKCS7_SIGNED;
|
||||
|
||||
typedef struct {
|
||||
STACK_OF(X509) *cert;
|
||||
STACK_OF(X509_CRL) *crl;
|
||||
} PKCS7_SIGN_ENVELOPE;
|
||||
|
||||
typedef void PKCS7_ENVELOPE;
|
||||
typedef void PKCS7_DIGEST;
|
||||
typedef void PKCS7_ENCRYPT;
|
||||
|
||||
typedef struct {
|
||||
uint8_t *ber_bytes;
|
||||
size_t ber_len;
|
||||
|
||||
// Unlike OpenSSL, the following fields are immutable. They filled in when the
|
||||
// object is parsed and ignored in serialization.
|
||||
ASN1_OBJECT *type;
|
||||
union {
|
||||
char *ptr;
|
||||
ASN1_OCTET_STRING *data;
|
||||
PKCS7_SIGNED *sign;
|
||||
PKCS7_ENVELOPE *enveloped;
|
||||
PKCS7_SIGN_ENVELOPE *signed_and_enveloped;
|
||||
PKCS7_DIGEST *digest;
|
||||
PKCS7_ENCRYPT *encrypted;
|
||||
ASN1_TYPE *other;
|
||||
} d;
|
||||
} PKCS7;
|
||||
|
||||
// d2i_PKCS7 parses a BER-encoded, PKCS#7 signed data ContentInfo structure from
|
||||
// |len| bytes at |*inp|. If |out| is not NULL then, on exit, a pointer to the
|
||||
// result is in |*out|. Note that, even if |*out| is already non-NULL on entry,
|
||||
// it will not be written to. Rather, a fresh |PKCS7| is allocated and the
|
||||
// previous one is freed. On successful exit, |*inp| is advanced past the BER
|
||||
// structure. It returns the result or NULL on error.
|
||||
OPENSSL_EXPORT PKCS7 *d2i_PKCS7(PKCS7 **out, const uint8_t **inp,
|
||||
size_t len);
|
||||
|
||||
// d2i_PKCS7_bio behaves like |d2i_PKCS7| but reads the input from |bio|. If
|
||||
// the length of the object is indefinite the full contents of |bio| are read.
|
||||
//
|
||||
// If the function fails then some unknown amount of data may have been read
|
||||
// from |bio|.
|
||||
OPENSSL_EXPORT PKCS7 *d2i_PKCS7_bio(BIO *bio, PKCS7 **out);
|
||||
|
||||
// i2d_PKCS7 is a dummy function which copies the contents of |p7|. If |out| is
|
||||
// not NULL then the result is written to |*out| and |*out| is advanced just
|
||||
// past the output. It returns the number of bytes in the result, whether
|
||||
// written or not, or a negative value on error.
|
||||
OPENSSL_EXPORT int i2d_PKCS7(const PKCS7 *p7, uint8_t **out);
|
||||
|
||||
// i2d_PKCS7_bio writes |p7| to |bio|. It returns one on success and zero on
|
||||
// error.
|
||||
OPENSSL_EXPORT int i2d_PKCS7_bio(BIO *bio, const PKCS7 *p7);
|
||||
|
||||
// PKCS7_free releases memory associated with |p7|.
|
||||
OPENSSL_EXPORT void PKCS7_free(PKCS7 *p7);
|
||||
|
||||
// PKCS7_type_is_data returns zero.
|
||||
OPENSSL_EXPORT int PKCS7_type_is_data(const PKCS7 *p7);
|
||||
|
||||
// PKCS7_type_is_digest returns zero.
|
||||
OPENSSL_EXPORT int PKCS7_type_is_digest(const PKCS7 *p7);
|
||||
|
||||
// PKCS7_type_is_encrypted returns zero.
|
||||
OPENSSL_EXPORT int PKCS7_type_is_encrypted(const PKCS7 *p7);
|
||||
|
||||
// PKCS7_type_is_enveloped returns zero.
|
||||
OPENSSL_EXPORT int PKCS7_type_is_enveloped(const PKCS7 *p7);
|
||||
|
||||
// PKCS7_type_is_signed returns one. (We only supporte signed data
|
||||
// ContentInfos.)
|
||||
OPENSSL_EXPORT int PKCS7_type_is_signed(const PKCS7 *p7);
|
||||
|
||||
// PKCS7_type_is_signedAndEnveloped returns zero.
|
||||
OPENSSL_EXPORT int PKCS7_type_is_signedAndEnveloped(const PKCS7 *p7);
|
||||
|
||||
// PKCS7_DETACHED indicates that the PKCS#7 file specifies its data externally.
|
||||
#define PKCS7_DETACHED 0x40
|
||||
|
||||
// The following flags cause |PKCS7_sign| to fail.
|
||||
#define PKCS7_TEXT 0x1
|
||||
#define PKCS7_NOCERTS 0x2
|
||||
#define PKCS7_NOSIGS 0x4
|
||||
#define PKCS7_NOCHAIN 0x8
|
||||
#define PKCS7_NOINTERN 0x10
|
||||
#define PKCS7_NOVERIFY 0x20
|
||||
#define PKCS7_BINARY 0x80
|
||||
#define PKCS7_NOATTR 0x100
|
||||
#define PKCS7_NOSMIMECAP 0x200
|
||||
#define PKCS7_STREAM 0x1000
|
||||
|
||||
// PKCS7_sign assembles |certs| into a PKCS#7 signed data ContentInfo with
|
||||
// external data and no signatures. It returns a newly-allocated |PKCS7| on
|
||||
// success or NULL on error. |sign_cert| and |pkey| must be NULL. |data| is
|
||||
// ignored. |flags| must be equal to |PKCS7_DETACHED|.
|
||||
//
|
||||
// Note this function only implements a subset of the corresponding OpenSSL
|
||||
// function. It is provided for backwards compatibility only.
|
||||
OPENSSL_EXPORT PKCS7 *PKCS7_sign(X509 *sign_cert, EVP_PKEY *pkey,
|
||||
STACK_OF(X509) *certs, BIO *data, int flags);
|
||||
|
||||
|
||||
#if defined(__cplusplus)
|
||||
} // extern C
|
||||
|
||||
extern "C++" {
|
||||
namespace bssl {
|
||||
|
||||
BORINGSSL_MAKE_DELETER(PKCS7, PKCS7_free)
|
||||
|
||||
} // namespace bssl
|
||||
} // extern C++
|
||||
#endif
|
||||
|
||||
#define PKCS7_R_BAD_PKCS7_VERSION 100
|
||||
|
Loading…
Reference in New Issue
Block a user