Skip to content

Commit

Permalink
Merge pull request #1262 from nebeid/upstream-merge-2022-10-23
Browse files Browse the repository at this point in the history
Upstream merge 2023-10-23
  • Loading branch information
nebeid authored Oct 31, 2023
2 parents 8836748 + 31dd5f3 commit 4da3dab
Show file tree
Hide file tree
Showing 25 changed files with 492 additions and 344 deletions.
4 changes: 4 additions & 0 deletions crypto/curve25519/x25519_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ TEST(X25519Test, Wycheproof) {

#if defined(BORINGSSL_X25519_NEON) && defined(SUPPORTS_ABI_TEST)
TEST(X25519Test, NeonABI) {
if (!CRYPTO_is_NEON_capable()) {
GTEST_SKIP() << "Can't test ABI of NEON code without NEON";
}

static const uint8_t kScalar[32] = {
0xa5, 0x46, 0xe3, 0x6b, 0xf0, 0x52, 0x7c, 0x9d, 0x3b, 0x16, 0x15,
0x4b, 0x82, 0x46, 0x5e, 0xdd, 0x62, 0x14, 0x4c, 0x0a, 0xc1, 0xfc,
Expand Down
6 changes: 5 additions & 1 deletion crypto/evp_extra/evp_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ static int GetKeyType(FileTest *t, const std::string &name) {
return EVP_PKEY_NONE;
}

static int GetRSAPadding(FileTest *t, int *out, const std::string &name) {
static bool GetRSAPadding(FileTest *t, int *out, const std::string &name) {
if (name == "PKCS1") {
*out = RSA_PKCS1_PADDING;
return true;
Expand All @@ -157,6 +157,10 @@ static int GetRSAPadding(FileTest *t, int *out, const std::string &name) {
*out = RSA_PKCS1_OAEP_PADDING;
return true;
}
if (name == "None") {
*out = RSA_NO_PADDING;
return true;
}
ADD_FAILURE() << "Unknown RSA padding mode: " << name;
return false;
}
Expand Down
25 changes: 25 additions & 0 deletions crypto/evp_extra/evp_tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,21 @@ PublicKey = P-256-SPKI
Input = 305b301506072a8648ce3d020106082a8648ce3d0301070500034200042c150f429ce70f216c252cf5e062ce1f639cd5d165c7f89424072c27197d78b33b920e95cdb664e990dcf0cfea0d94e2a8e6af9d0e58056e653104925b9fe6c9
Error = DECODE_ERROR

PrivateKey = P-224-ExplicitParameters
Input = 308201540201003081eb06072a8648ce3d02013081df020101302806072a8648ce3d0101021d00ffffffffffffffffffffffffffffffff0000000000000000000000013053041cfffffffffffffffffffffffffffffffefffffffffffffffffffffffe041cb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4031500bd71344799d5c7fcdc45b59fa3b9ab8f6a948bc5043904b70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21bd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34021d00ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d0201010461305f020101041caa17cf0a0c8064bf6b509c212e48293d61e0d97acc85f8dea5c99166a13c033a00041a95d8c8d08635ad7fa0facd3a9fe56f8f42451b58ea8a29f7d28b0fb214690b1149a69f016f644cdb3320b78a381027835d6091903f0513
Type = EC
Output = 3078020100301006072a8648ce3d020106052b810400210461305f020101041caa17cf0a0c8064bf6b509c212e48293d61e0d97acc85f8dea5c99166a13c033a00041a95d8c8d08635ad7fa0facd3a9fe56f8f42451b58ea8a29f7d28b0fb214690b1149a69f016f644cdb3320b78a381027835d6091903f0513

PrivateKey = P-384-ExplicitParameters
Input = 3082020c0201003082016406072a8648ce3d020130820157020101303c06072a8648ce3d0101023100fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff307b0430fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc0430b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef031500a335926aa319a27a1d00896a6773a4827acdac73046104aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab73617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f023100ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc5297302010104819e30819b0201010430a29991b86091401328e62ec8caadd0482f887ff0936910e42a56c19f48cbe87331037a4e2b36f1091dd4a26ee2d2b01fa16403620004826b3df548ad2e0b96436cb13508e88745a33b4b06cf485ad8350824b4dfe01ee66a5e1d1aaebfebcaa6337a1f33c338afc0d59b7ce7e389f73f66c9c4a44bbfcf570aec5cc52e7b6608c9061ab4d72de933448c39dd9238177917d398c22c5e
Type = EC
Output = 3081b6020100301006072a8648ce3d020106052b8104002204819e30819b0201010430a29991b86091401328e62ec8caadd0482f887ff0936910e42a56c19f48cbe87331037a4e2b36f1091dd4a26ee2d2b01fa16403620004826b3df548ad2e0b96436cb13508e88745a33b4b06cf485ad8350824b4dfe01ee66a5e1d1aaebfebcaa6337a1f33c338afc0d59b7ce7e389f73f66c9c4a44bbfcf570aec5cc52e7b6608c9061ab4d72de933448c39dd9238177917d398c22c5e

PrivateKey = P-521-ExplicitParameters
Input = 308202b0020100308201d006072a8648ce3d0201308201c3020101304d06072a8648ce3d0101024201ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff30819f044201fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc04420051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00031500d09e8800291cb85396cc6717393284aaa0da64ba0481850400c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650024201fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e913864090201010481d63081d30201010442002eeee9c16b9b4d2dee606f443cf3b41d2899a4734ff7555b54c735afb34a6912f81c68a89ea9b427c69a1026d98ef1d7f9c683aec5c5103d9a4c21e403c638412fa18189038186000400f58adcbe5c07f6b500fadd3209487e38f9567f97c2204435a4eb140739905c201407e2530a6667216aad01fb849bcbefa3862b2f187f13c9a87923d378a0a184df017b4bb93f4766531785878458da6aaa525724e10dfb1f35cfbe55c56fc705714295ea74b6c3e714152ad78e929f5415683aed9bc7c68f0329934177829d715f03f2
Type = EC
Output = 3081ee020100301006072a8648ce3d020106052b810400230481d63081d30201010442002eeee9c16b9b4d2dee606f443cf3b41d2899a4734ff7555b54c735afb34a6912f81c68a89ea9b427c69a1026d98ef1d7f9c683aec5c5103d9a4c21e403c638412fa18189038186000400f58adcbe5c07f6b500fadd3209487e38f9567f97c2204435a4eb140739905c201407e2530a6667216aad01fb849bcbefa3862b2f187f13c9a87923d378a0a184df017b4bb93f4766531785878458da6aaa525724e10dfb1f35cfbe55c56fc705714295ea74b6c3e714152ad78e929f5415683aed9bc7c68f0329934177829d715f03f2

# A DSA private key.
PrivateKey = DSA-1024
Type = DSA
Expand Down Expand Up @@ -772,6 +787,16 @@ Digest = SHA256
Input = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
Output = 07fa4e3de9c002c41c952dc292ef5a814c4c17dc1a6cf958c4c971e8089676d6661b442270ef9295c41e5385c9628aa1bdee2cc2558b8473ba212f2ba04b9ff2264c19187b9506b1d0a1cc2751844cc8dedf555d62ce81bc0e70bfe83d0184ee964593af91b9b327c0fb272c799148cd8737d412cbf36c2ad25fd66977bf805f

# The input is 1 (mod p) and q - 1 (mod q). If the CRT implementation does not
# account for p < q, the subtraction step will break. However, this test only
# works when RSA blinding is disabled. When blinding is enabled, these values
# are randomized and, instead, either this or the above test will hit this case
# if repeated enough. (It hits it with probability (q-p)^2 / 2pq, or 1.8% here.)
Encrypt = RSA-Swapped
RSAPadding = None
Input = 7ab490e1f5e718ff23a9e738f9867559e3a6ea72332e3bcc1b6f58585218ed815a865dab75e3f44ea550bdef815101d6039251a513fd99188c1916abb94b15ef72de793f6472a342b33a125cfc96a4fc89d140e337f5fe6ff937ed3f34b6b09f5227f598e12faddf4423254acbee748197bed26954502c7484c65e279fed7fed
CheckDecrypt

# Though we will never generate such a key, test that RSA keys where p and q are
# different sizes work properly.
PrivateKey = RSA-PrimeMismatch
Expand Down
1 change: 1 addition & 0 deletions crypto/evp_extra/p_rsa_asn1.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@

#include "../rsa_extra/internal.h"
#include "../fipsmodule/evp/internal.h"
#include "../fipsmodule/rsa/internal.h"
#include "internal.h"

static int rsa_pub_encode(CBB *out, const EVP_PKEY *key) {
Expand Down
17 changes: 12 additions & 5 deletions crypto/fipsmodule/bn/div.c
Original file line number Diff line number Diff line change
Expand Up @@ -711,15 +711,22 @@ int BN_mod_lshift(BIGNUM *r, const BIGNUM *a, int n, const BIGNUM *m,

int bn_mod_lshift_consttime(BIGNUM *r, const BIGNUM *a, int n, const BIGNUM *m,
BN_CTX *ctx) {
if (!BN_copy(r, a)) {
if (!BN_copy(r, a) ||
!bn_resize_words(r, m->width)) {
return 0;
}
for (int i = 0; i < n; i++) {
if (!bn_mod_lshift1_consttime(r, r, m, ctx)) {
return 0;

BN_CTX_start(ctx);
BIGNUM *tmp = bn_scratch_space_from_ctx(m->width, ctx);
int ok = tmp != NULL;
if (ok) {
for (int i = 0; i < n; i++) {
bn_mod_add_words(r->d, r->d, r->d, m->d, tmp->d, m->width);
}
r->neg = 0;
}
return 1;
BN_CTX_end(ctx);
return ok;
}

int BN_mod_lshift_quick(BIGNUM *r, const BIGNUM *a, int n, const BIGNUM *m) {
Expand Down
11 changes: 5 additions & 6 deletions crypto/fipsmodule/bn/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -435,12 +435,11 @@ void bn_power5(BN_ULONG *rp, const BN_ULONG *ap, const BN_ULONG *table,

uint64_t bn_mont_n0(const BIGNUM *n);

// bn_mod_exp_base_2_consttime calculates r = 2**p (mod n). |p| must be larger
// than log_2(n); i.e. 2**p must be larger than |n|. |n| must be positive and
// odd. |p| and the bit width of |n| are assumed public, but |n| is otherwise
// treated as secret.
int bn_mod_exp_base_2_consttime(BIGNUM *r, unsigned p, const BIGNUM *n,
BN_CTX *ctx);
// bn_mont_ctx_set_RR_consttime initializes |mont->RR|. It returns one on
// success and zero on error. |mont->N| and |mont->n0| must have been
// initialized already. The bit width of |mont->N| is assumed public, but
// |mont->N| is otherwise treated as secret.
int bn_mont_ctx_set_RR_consttime(BN_MONT_CTX *mont, BN_CTX *ctx);

#if defined(_MSC_VER)
#if defined(OPENSSL_X86_64)
Expand Down
15 changes: 4 additions & 11 deletions crypto/fipsmodule/bn/montgomery.c
Original file line number Diff line number Diff line change
Expand Up @@ -279,19 +279,12 @@ BN_MONT_CTX *BN_MONT_CTX_new_for_modulus(const BIGNUM *mod, BN_CTX *ctx) {
BN_MONT_CTX *BN_MONT_CTX_new_consttime(const BIGNUM *mod, BN_CTX *ctx) {
BN_MONT_CTX *mont = BN_MONT_CTX_new();
if (mont == NULL ||
!bn_mont_ctx_set_N_and_n0(mont, mod)) {
goto err;
}
unsigned lgBigR = mont->N.width * BN_BITS2;
if (!bn_mod_exp_base_2_consttime(&mont->RR, lgBigR * 2, &mont->N, ctx) ||
!bn_resize_words(&mont->RR, mont->N.width)) {
goto err;
!bn_mont_ctx_set_N_and_n0(mont, mod) ||
!bn_mont_ctx_set_RR_consttime(mont, ctx)) {
BN_MONT_CTX_free(mont);
return NULL;
}
return mont;

err:
BN_MONT_CTX_free(mont);
return NULL;
}

int BN_MONT_CTX_set_locked(BN_MONT_CTX **pmont, CRYPTO_MUTEX *lock,
Expand Down
66 changes: 51 additions & 15 deletions crypto/fipsmodule/bn/montgomery_inv.c
Original file line number Diff line number Diff line change
Expand Up @@ -160,27 +160,63 @@ static uint64_t bn_neg_inv_mod_r_u64(uint64_t n) {
return v;
}

int bn_mod_exp_base_2_consttime(BIGNUM *r, unsigned p, const BIGNUM *n,
BN_CTX *ctx) {
assert(!BN_is_zero(n));
assert(!BN_is_negative(n));
assert(BN_is_odd(n));
int bn_mont_ctx_set_RR_consttime(BN_MONT_CTX *mont, BN_CTX *ctx) {
assert(!BN_is_zero(&mont->N));
assert(!BN_is_negative(&mont->N));
assert(BN_is_odd(&mont->N));
assert(bn_minimal_width(&mont->N) == mont->N.width);

BN_zero(r);

unsigned n_bits = BN_num_bits(n);
unsigned n_bits = BN_num_bits(&mont->N);
assert(n_bits != 0);
assert(p > n_bits);
if (n_bits == 1) {
return 1;
BN_zero(&mont->RR);
return bn_resize_words(&mont->RR, mont->N.width);
}

// Set |r| to the larger power of two smaller than |n|, then shift with
// reductions the rest of the way.
if (!BN_set_bit(r, n_bits - 1) ||
!bn_mod_lshift_consttime(r, r, p - (n_bits - 1), n, ctx)) {
unsigned lgBigR = mont->N.width * BN_BITS2;
assert(lgBigR >= n_bits);

// RR is R, or 2^lgBigR, in the Montgomery domain. We can compute 2 in the
// Montgomery domain, 2R or 2^(lgBigR+1), and then use Montgomery
// square-and-multiply to exponentiate.
//
// The multiply steps take 2^n R to 2^(n+1) R. It is faster to double
// the value instead. The square steps take 2^n R to 2^(2n) R. This is
// equivalent to doubling n times. When n is below some threshold, doubling is
// faster. When above, squaring is faster.
//
// We double to this threshold, then switch to Montgomery squaring. From
// benchmarking various 32-bit and 64-bit architectures, the word count seems
// to work well as a threshold. (Doubling scales linearly and Montgomery
// reduction scales quadratically, so the threshold should scale roughly
// linearly.)
unsigned threshold = mont->N.width;
unsigned iters;
for (iters = 0; iters < sizeof(lgBigR) * 8; iters++) {
if ((lgBigR >> iters) <= threshold) {
break;
}
}

// Compute 2^(lgBigR >> iters) R, or 2^((lgBigR >> iters) + lgBigR), by
// doubling. The first n_bits - 1 doubles can be skipped because we don't need
// to reduce.
if (!BN_set_bit(&mont->RR, n_bits - 1) ||
!bn_mod_lshift_consttime(&mont->RR, &mont->RR,
(lgBigR >> iters) + lgBigR - (n_bits - 1),
&mont->N, ctx)) {
return 0;
}

return 1;
for (unsigned i = iters - 1; i < iters; i--) {
if (!BN_mod_mul_montgomery(&mont->RR, &mont->RR, &mont->RR, mont, ctx)) {
return 0;
}
if ((lgBigR & (1u << i)) != 0 &&
!bn_mod_lshift1_consttime(&mont->RR, &mont->RR, &mont->N, ctx)) {
return 0;
}
}

return bn_resize_words(&mont->RR, mont->N.width);
}
49 changes: 37 additions & 12 deletions crypto/fipsmodule/evp/evp.c
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,13 @@ static const EVP_PKEY_ASN1_METHOD *evp_pkey_asn1_find(int nid) {
return NULL;
}

static void evp_pkey_set_method(EVP_PKEY *pkey,
const EVP_PKEY_ASN1_METHOD *method) {
free_it(pkey);
pkey->ameth = method;
pkey->type = pkey->ameth->pkey_id;
}

int EVP_PKEY_type(int nid) {
const EVP_PKEY_ASN1_METHOD *meth = evp_pkey_asn1_find(nid);
if (meth == NULL) {
Expand All @@ -244,7 +251,9 @@ int EVP_PKEY_set1_RSA(EVP_PKEY *pkey, RSA *key) {
}

int EVP_PKEY_assign_RSA(EVP_PKEY *pkey, RSA *key) {
return EVP_PKEY_assign(pkey, EVP_PKEY_RSA, key);
evp_pkey_set_method(pkey, evp_pkey_asn1_find(EVP_PKEY_RSA));
pkey->pkey.ptr = key;
return key != NULL;
}

RSA *EVP_PKEY_get0_RSA(const EVP_PKEY *pkey) {
Expand Down Expand Up @@ -272,7 +281,9 @@ int EVP_PKEY_set1_DSA(EVP_PKEY *pkey, DSA *key) {
}

int EVP_PKEY_assign_DSA(EVP_PKEY *pkey, DSA *key) {
return EVP_PKEY_assign(pkey, EVP_PKEY_DSA, key);
evp_pkey_set_method(pkey, evp_pkey_asn1_find(EVP_PKEY_DSA));
pkey->pkey.ptr = key;
return key != NULL;
}

DSA *EVP_PKEY_get0_DSA(const EVP_PKEY *pkey) {
Expand Down Expand Up @@ -300,7 +311,9 @@ int EVP_PKEY_set1_EC_KEY(EVP_PKEY *pkey, EC_KEY *key) {
}

int EVP_PKEY_assign_EC_KEY(EVP_PKEY *pkey, EC_KEY *key) {
return EVP_PKEY_assign(pkey, EVP_PKEY_EC, key);
evp_pkey_set_method(pkey, evp_pkey_asn1_find(EVP_PKEY_EC));
pkey->pkey.ptr = key;
return key != NULL;
}

EC_KEY *EVP_PKEY_get0_EC_KEY(const EVP_PKEY *pkey) {
Expand All @@ -323,30 +336,42 @@ DH *EVP_PKEY_get0_DH(const EVP_PKEY *pkey) { return NULL; }
DH *EVP_PKEY_get1_DH(const EVP_PKEY *pkey) { return NULL; }

int EVP_PKEY_assign(EVP_PKEY *pkey, int type, void *key) {
if (!EVP_PKEY_set_type(pkey, type)) {
return 0;
// This function can only be used to assign RSA, DSA, and EC keys. Other key
// types have internal representations which are not exposed through the
// public API.
switch (type) {
case EVP_PKEY_RSA:
return EVP_PKEY_assign_RSA(pkey, key);
case EVP_PKEY_DSA:
return EVP_PKEY_assign_DSA(pkey, key);
case EVP_PKEY_EC:
return EVP_PKEY_assign_EC_KEY(pkey, key);
default:
if (!EVP_PKEY_set_type(pkey, type)) {
return 0;
}
pkey->pkey.ptr = key;
return key != NULL;
}
pkey->pkey.ptr = key;
return key != NULL;
}

int EVP_PKEY_set_type(EVP_PKEY *pkey, int type) {
const EVP_PKEY_ASN1_METHOD *ameth;

if (pkey && pkey->pkey.ptr) {
// This isn't strictly necessary, but historically |EVP_PKEY_set_type| would
// clear |pkey| even if |evp_pkey_asn1_find| failed, so we preserve that
// behavior.
free_it(pkey);
}

ameth = evp_pkey_asn1_find(type);
const EVP_PKEY_ASN1_METHOD *ameth = evp_pkey_asn1_find(type);
if (ameth == NULL) {
OPENSSL_PUT_ERROR(EVP, EVP_R_UNSUPPORTED_ALGORITHM);
ERR_add_error_dataf("algorithm %d", type);
return 0;
}

if (pkey) {
pkey->ameth = ameth;
pkey->type = pkey->ameth->pkey_id;
evp_pkey_set_method(pkey, ameth);
}

return 1;
Expand Down
59 changes: 59 additions & 0 deletions crypto/fipsmodule/rsa/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,65 @@ typedef enum {
RSA_PUBLIC_KEY
} rsa_asn1_key_encoding_t;

typedef struct bn_blinding_st BN_BLINDING;

struct rsa_st {
RSA_METHOD *meth;

BIGNUM *n;
BIGNUM *e;
BIGNUM *d;
BIGNUM *p;
BIGNUM *q;
BIGNUM *dmp1;
BIGNUM *dmq1;
BIGNUM *iqmp;

// If a PSS only key this contains the parameter restrictions.
RSASSA_PSS_PARAMS *pss;

// be careful using this if the RSA structure is shared
CRYPTO_EX_DATA ex_data;
CRYPTO_refcount_t references;
int flags;

CRYPTO_MUTEX lock;

// Used to cache montgomery values. The creation of these values is protected
// by |lock|.
BN_MONT_CTX *mont_n;
BN_MONT_CTX *mont_p;
BN_MONT_CTX *mont_q;

// The following fields are copies of |d|, |dmp1|, and |dmq1|, respectively,
// but with the correct widths to prevent side channels. These must use
// separate copies due to threading concerns caused by OpenSSL's API
// mistakes. See https://github.com/openssl/openssl/issues/5158 and
// the |freeze_private_key| implementation.
BIGNUM *d_fixed, *dmp1_fixed, *dmq1_fixed;

// iqmp_mont is q^-1 mod p in Montgomery form, using |mont_p|.
BIGNUM *iqmp_mont;

// num_blindings contains the size of the |blindings| and |blindings_inuse|
// arrays. This member and the |blindings_inuse| array are protected by
// |lock|.
size_t num_blindings;
// blindings is an array of BN_BLINDING structures that can be reserved by a
// thread by locking |lock| and changing the corresponding element in
// |blindings_inuse| from 0 to 1.
BN_BLINDING **blindings;
unsigned char *blindings_inuse;
uint64_t blinding_fork_generation;

// private_key_frozen is one if the key has been used for a private key
// operation and may no longer be mutated.
unsigned private_key_frozen:1;
};


#define RSA_PKCS1_PADDING_SIZE 11

// Default implementations of RSA operations.

const RSA_METHOD *RSA_default_method(void);
Expand Down
Loading

0 comments on commit 4da3dab

Please sign in to comment.