Browse Source

Merge branch 'master' into dev

# Conflicts:
#	source/WinSCP.cbproj

Source commit: 6ce7aa9763eab8af4d4dcfc1a2d6a67065bd6407
Martin Prikryl 1 year ago
parent
commit
d47cc54280

+ 3 - 0
source/Putty.cbproj

@@ -190,6 +190,9 @@
 		<CppCompile Include="putty\crypto\pubkey-ppk.c">
 			<BuildOrder>31</BuildOrder>
 		</CppCompile>
+		<CppCompile Include="putty\crypto\rfc6979.c">
+			<BuildOrder>165</BuildOrder>
+		</CppCompile>
 		<CppCompile Include="putty\crypto\rsa.c">
 			<BuildOrder>32</BuildOrder>
 		</CppCompile>

+ 3 - 117
source/putty/crypto/dsa.c

@@ -350,121 +350,6 @@ static int dsa_pubkey_bits(const ssh_keyalg *self, ptrlen pub)
     return ret;
 }
 
-mp_int *dsa_gen_k(const char *id_string, mp_int *modulus,
-                  mp_int *private_key,
-                  unsigned char *digest, int digest_len)
-{
-    /*
-     * The basic DSA signing algorithm is:
-     *
-     *  - invent a random k between 1 and q-1 (exclusive).
-     *  - Compute r = (g^k mod p) mod q.
-     *  - Compute s = k^-1 * (hash + x*r) mod q.
-     *
-     * This has the dangerous properties that:
-     *
-     *  - if an attacker in possession of the public key _and_ the
-     *    signature (for example, the host you just authenticated
-     *    to) can guess your k, he can reverse the computation of s
-     *    and work out x = r^-1 * (s*k - hash) mod q. That is, he
-     *    can deduce the private half of your key, and masquerade
-     *    as you for as long as the key is still valid.
-     *
-     *  - since r is a function purely of k and the public key, if
-     *    the attacker only has a _range of possibilities_ for k
-     *    it's easy for him to work through them all and check each
-     *    one against r; he'll never be unsure of whether he's got
-     *    the right one.
-     *
-     *  - if you ever sign two different hashes with the same k, it
-     *    will be immediately obvious because the two signatures
-     *    will have the same r, and moreover an attacker in
-     *    possession of both signatures (and the public key of
-     *    course) can compute k = (hash1-hash2) * (s1-s2)^-1 mod q,
-     *    and from there deduce x as before.
-     *
-     *  - the Bleichenbacher attack on DSA makes use of methods of
-     *    generating k which are significantly non-uniformly
-     *    distributed; in particular, generating a 160-bit random
-     *    number and reducing it mod q is right out.
-     *
-     * For this reason we must be pretty careful about how we
-     * generate our k. Since this code runs on Windows, with no
-     * particularly good system entropy sources, we can't trust our
-     * RNG itself to produce properly unpredictable data. Hence, we
-     * use a totally different scheme instead.
-     *
-     * What we do is to take a SHA-512 (_big_) hash of the private
-     * key x, and then feed this into another SHA-512 hash that
-     * also includes the message hash being signed. That is:
-     *
-     *   proto_k = SHA512 ( SHA512(x) || SHA160(message) )
-     *
-     * This number is 512 bits long, so reducing it mod q won't be
-     * noticeably non-uniform. So
-     *
-     *   k = proto_k mod q
-     *
-     * This has the interesting property that it's _deterministic_:
-     * signing the same hash twice with the same key yields the
-     * same signature.
-     *
-     * Despite this determinism, it's still not predictable to an
-     * attacker, because in order to repeat the SHA-512
-     * construction that created it, the attacker would have to
-     * know the private key value x - and by assumption he doesn't,
-     * because if he knew that he wouldn't be attacking k!
-     *
-     * (This trick doesn't, _per se_, protect against reuse of k.
-     * Reuse of k is left to chance; all it does is prevent
-     * _excessively high_ chances of reuse of k due to entropy
-     * problems.)
-     *
-     * Thanks to Colin Plumb for the general idea of using x to
-     * ensure k is hard to guess, and to the Cambridge University
-     * Computer Security Group for helping to argue out all the
-     * fine details.
-     */
-    ssh_hash *h;
-    unsigned char digest512[64];
-
-    /*
-     * Hash some identifying text plus x.
-     */
-    h = ssh_hash_new(&ssh_sha512);
-    put_asciz(h, id_string);
-    put_mp_ssh2(h, private_key);
-    ssh_hash_digest(h, digest512);
-
-    /*
-     * Now hash that digest plus the message hash.
-     */
-    ssh_hash_reset(h);
-    put_data(h, digest512, sizeof(digest512));
-    put_data(h, digest, digest_len);
-    ssh_hash_final(h, digest512);
-
-    /*
-     * Now convert the result into a bignum, and coerce it to the
-     * range [2,q), which we do by reducing it mod q-2 and adding 2.
-     */
-    { // WINSCP
-    mp_int *modminus2 = mp_copy(modulus);
-    mp_sub_integer_into(modminus2, modminus2, 2);
-    { // WINSCP
-    mp_int *proto_k = mp_from_bytes_be(make_ptrlen(digest512, 64));
-    mp_int *k = mp_mod(proto_k, modminus2);
-    mp_free(proto_k);
-    mp_free(modminus2);
-    mp_add_integer_into(k, k, 2);
-
-    smemclr(digest512, sizeof(digest512));
-
-    return k;
-    } // WINSCP
-    } // WINSCP
-}
-
 static void dsa_sign(ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs)
 {
     struct dsa_key *dsa = container_of(key, struct dsa_key, sshk);
@@ -474,8 +359,9 @@ static void dsa_sign(ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs)
     hash_simple(&ssh_sha1, data, digest);
 
     { // WINSCP
-    mp_int *k = dsa_gen_k("DSA deterministic k generator", dsa->q, dsa->x,
-                          digest, sizeof(digest));
+    /* Generate any valid exponent k, using the RFC 6979 deterministic
+     * procedure. */
+    mp_int *k = rfc6979(&ssh_sha1, dsa->q, dsa->x, data);
     mp_int *kinv = mp_invert(k, dsa->q);       /* k^-1 mod q */
 
     /*

+ 4 - 10
source/putty/crypto/ecc-ssh.c

@@ -1276,16 +1276,10 @@ static void ecdsa_sign(ssh_key *key, ptrlen data,
     { // WINSCP
     mp_int *z = ecdsa_signing_exponent_from_data(ek->curve, extra, data);
 
-    /* Generate k between 1 and curve->n, using the same deterministic
-     * k generation system we use for conventional DSA. */
-    mp_int *k;
-    {
-        unsigned char digest[20];
-        hash_simple(&ssh_sha1, data, digest);
-        k = dsa_gen_k(
-            "ECDSA deterministic k generator", ek->curve->w.G_order,
-            ek->privateKey, digest, sizeof(digest));
-    }
+    /* Generate any valid exponent k, using the RFC 6979 deterministic
+     * procedure. */
+    mp_int *k = rfc6979(
+        extra->hash, ek->curve->w.G_order, ek->privateKey, data);
 
     { // WINSCP
     WeierstrassPoint *kG = ecc_weierstrass_multiply(ek->curve->w.G, k);

+ 43 - 2
source/putty/crypto/hmac.c

@@ -18,9 +18,10 @@ struct hmac_extra {
     const char *suffix, *annotation;
 };
 
-static ssh2_mac *hmac_new(const ssh2_macalg *alg, ssh_cipher *cipher)
+/* Most of hmac_new(). Takes the actual 'struct hmac' as a parameter,
+ * because sometimes it will have been allocated in a special way. */
+static ssh2_mac *hmac_new_inner(struct hmac *ctx, const ssh2_macalg *alg)
 {
-    struct hmac *ctx = snew(struct hmac);
     const struct hmac_extra *extra = (const struct hmac_extra *)alg->extra;
 
     ctx->h_outer = ssh_hash_new(extra->hashalg_base);
@@ -66,6 +67,11 @@ static ssh2_mac *hmac_new(const ssh2_macalg *alg, ssh_cipher *cipher)
     return &ctx->mac;
 }
 
+static ssh2_mac *hmac_new(const ssh2_macalg *alg, ssh_cipher *cipher)
+{
+    return hmac_new_inner(snew(struct hmac), alg); /* cipher isn't needed */
+}
+
 static void hmac_free(ssh2_mac *mac)
 {
     struct hmac *ctx = container_of(mac, struct hmac, mac);
@@ -292,3 +298,38 @@ const ssh2_macalg ssh_hmac_sha1_96_buggy = {
     /*.keylen =*/ 16,
     /*.extra =*/ &ssh_hmac_sha1_96_buggy_extra,
 };
+
+ssh2_mac *hmac_new_from_hash(const ssh_hashalg *hash)
+{
+    /*
+     * Construct a custom ssh2_macalg, derived directly from the
+     * provided hash vtable. It's included in the same memory
+     * allocation as the struct hmac, so that it all gets freed
+     * together.
+     */
+
+    struct alloc {
+        struct hmac hmac;
+        ssh2_macalg alg;
+        struct hmac_extra extra;
+    };
+
+    struct alloc *alloc = snew(struct alloc);
+    alloc->alg.new = hmac_new;
+    alloc->alg.free = hmac_free;
+    alloc->alg.setkey = hmac_key;
+    alloc->alg.start = hmac_start;
+    alloc->alg.genresult = hmac_genresult;
+    alloc->alg.next_message = nullmac_next_message;
+    alloc->alg.text_name = hmac_text_name;
+    alloc->alg.name = NULL;
+    alloc->alg.etm_name = NULL;
+    alloc->alg.len = hash->hlen;
+    alloc->alg.keylen = hash->hlen;
+    alloc->alg.extra = &alloc->extra;
+    alloc->extra.hashalg_base = hash;
+    alloc->extra.suffix = "";
+    alloc->extra.annotation = NULL;
+
+    return hmac_new_inner(&alloc->hmac, &alloc->alg);
+}

+ 369 - 0
source/putty/crypto/rfc6979.c

@@ -0,0 +1,369 @@
+/*
+ * Code to generate 'nonce' values for DSA signature algorithms, in a
+ * deterministic way.
+ */
+
+#include "ssh.h"
+#include "mpint.h"
+#include "misc.h"
+
+/*
+ * All DSA-type signature systems depend on a nonce - a random number
+ * generated during the signing operation.
+ *
+ * This nonce is a weak point of DSA and needs careful protection,
+ * for multiple reasons:
+ *
+ *  1. If an attacker in possession of your public key and a single
+ *     signature can find out or guess the nonce you used in that
+ *     signature, they can immediately recover your _private key_.
+ *
+ *  2. If you reuse the same nonce in two different signatures, this
+ *     will be instantly obvious to the attacker (one of the two
+ *     values making up the signature will match), and again, they can
+ *     immediately recover the private key as soon as they notice this.
+ *
+ *  3. In at least one system, information about your private key is
+ *     leaked merely by generating nonces with a significant bias.
+ *
+ * Attacks #1 and #2 work across all of integer DSA, NIST-style ECDSA,
+ * and EdDSA. The details vary, but the headline effects are the same.
+ *
+ * So we must be very careful with our nonces. They must be generated
+ * with uniform distribution, but also, they must avoid depending on
+ * any random number generator that has the slightest doubt about its
+ * reliability.
+ *
+ * In particular, PuTTY's policy is that for this purpose we don't
+ * _even_ trust the PRNG we use for other cryptography. This is mostly
+ * a concern because of Windows, where system entropy sources are
+ * limited and we have doubts about their trustworthiness
+ * - even CryptGenRandom. PuTTY compensates as best it can with its
+ * own ongoing entropy collection, and we trust that for session keys,
+ * but revealing the private key that goes with a long-term public key
+ * is a far worse outcome than revealing one SSH session key, and for
+ * keeping your private key safe, we don't think the available Windows
+ * entropy gives us enough confidence.
+ *
+ * A common strategy these days (although <hipster>PuTTY was doing it
+ * before it was cool</hipster>) is to avoid using a PRNG based on
+ * system entropy at all. Instead, you use a deterministic PRNG that
+ * starts from a fixed input seed, and in that input seed you include
+ * the message to be signed and the _private key_.
+ *
+ * Including the private key in the seed is counterintuitive, but does
+ * actually make sense. A deterministic nonce generation strategy must
+ * use _some_ piece of input that the attacker doesn't have, or else
+ * they'd be able to repeat the entire computation and construct the
+ * same nonce you did. And the one thing they don't know is the
+ * private key! So we include that in the seed data (under enough
+ * layers of overcautious hashing to protect it against exposure), and
+ * then they _can't_ repeat the same construction. Moreover, if they
+ * _could_, they'd already know the private key, so they wouldn't need
+ * to perform an attack of this kind at all!
+ *
+ * (This trick doesn't, _per se_, protect against reuse of nonces.
+ * That is left to chance, which is enough, because the space of
+ * nonces is large enough to make it adequately unlikely. But it
+ * avoids escalating the reuse risk due to inadequate entropy.)
+ *
+ * For integer DSA and ECDSA, the system we use for deterministic
+ * generation of k is exactly the one specified in RFC 6979. We
+ * switched to this from the old system that PuTTY used to use before
+ * that RFC came out. The old system had a critical bug: it did not
+ * always generate _enough_ data to get uniform distribution, because
+ * its output was a single SHA-512 hash. We could have fixed that
+ * minimally, by concatenating multiple hashes, but it seemed more
+ * sensible to switch to a system that comes with test vectors.
+ *
+ * One downside of RFC 6979 is that it's based on rejection sampling
+ * (that is, you generate a random number and keep retrying until it's
+ * in range). This makes it play badly with our side-channel test
+ * system, which wants every execution trace of a supposedly
+ * constant-time operation to be the same. To work around this
+ * awkwardness, we break up the algorithm further, into a setup phase
+ * and an 'attempt to generate an output' phase, each of which is
+ * individually constant-time.
+ */
+
+struct RFC6979 {
+    /*
+     * Size of the cyclic group over which we're doing DSA.
+     * Equivalently, the multiplicative order of g (for integer DSA)
+     * or the curve's base point (for ECDSA). For integer DSA this is
+     * also the same thing as the small prime q from the key
+     * parameters.
+     *
+     * This pointer is not owned. Freeing this structure will not free
+     * it, and freeing the pointed-to integer before freeing this
+     * structure will make this structure dangerous to use.
+     */
+    mp_int *q;
+
+    /*
+     * The private key integer, which is always the discrete log of
+     * the public key with respect to the group generator.
+     *
+     * This pointer is not owned. Freeing this structure will not free
+     * it, and freeing the pointed-to integer before freeing this
+     * structure will make this structure dangerous to use.
+     */
+    mp_int *x;
+
+    /*
+     * Cached values derived from q: its length in bits, and in bytes.
+     */
+    size_t qbits, qbytes;
+
+    /*
+     * Reusable hash and MAC objects.
+     */
+    ssh_hash *hash;
+    ssh2_mac *mac;
+
+    /*
+     * Cached value: the output length of the hash.
+     */
+    size_t hlen;
+
+    /*
+     * The byte string V used in the algorithm.
+     */
+    unsigned char V[MAX_HASH_LEN];
+
+    /*
+     * The string T to use during each attempt, and how many
+     * hash-sized blocks to fill it with.
+     */
+    size_t T_nblocks;
+    unsigned char *T;
+};
+
+static mp_int *bits2int(ptrlen b, RFC6979 *s)
+{
+    if (b.len > s->qbytes)
+        b.len = s->qbytes;
+    { // WINSCP
+    mp_int *x = mp_from_bytes_be(b);
+
+    /*
+     * Rationale for using mp_rshift_fixed_into and not
+     * mp_rshift_safe_into: the shift count is derived from the
+     * difference between the length of the modulus q, and the length
+     * of the input bit string, i.e. between the _sizes_ of things
+     * involved in the protocol. But the sizes aren't secret. Only the
+     * actual values of integers and bit strings of those sizes are
+     * secret. So it's OK for the shift count to be known to an
+     * attacker - they'd know it anyway just from which DSA algorithm
+     * we were using.
+     */
+    if (b.len * 8 > s->qbits)
+        mp_rshift_fixed_into(x, x, b.len * 8 - s->qbits);
+
+    return x;
+    } // WINSCP
+}
+
+static void BinarySink_put_int2octets(BinarySink *bs, mp_int *x, RFC6979 *s)
+{
+    mp_int *x_mod_q = mp_mod(x, s->q);
+    size_t i; // WINSCP
+    for (i = s->qbytes; i-- > 0 ;)
+        put_byte(bs, mp_get_byte(x_mod_q, i));
+    mp_free(x_mod_q);
+}
+
+static void BinarySink_put_bits2octets(BinarySink *bs, ptrlen b, RFC6979 *s)
+{
+    mp_int *x = bits2int(b, s);
+    BinarySink_put_int2octets(bs, x, s);
+    mp_free(x);
+}
+
+#define put_int2octets(bs, x, s) \
+    BinarySink_put_int2octets(BinarySink_UPCAST(bs), x, s)
+#define put_bits2octets(bs, b, s) \
+    BinarySink_put_bits2octets(BinarySink_UPCAST(bs), b, s)
+
+RFC6979 *rfc6979_new(const ssh_hashalg *hashalg, mp_int *q, mp_int *x)
+{
+    /* Make the state structure. */
+    RFC6979 *s = snew(RFC6979);
+    s->q = q;
+    s->x = x;
+    s->qbits = mp_get_nbits(q);
+    s->qbytes = (s->qbits + 7) >> 3;
+    s->hash = ssh_hash_new(hashalg);
+    s->mac = hmac_new_from_hash(hashalg);
+    s->hlen = hashalg->hlen;
+
+    /* In each attempt, we concatenate enough hash blocks to be
+     * greater than qbits in size. */
+    { // WINSCP
+    size_t hbits = 8 * s->hlen;
+    s->T_nblocks = (s->qbits + hbits - 1) / hbits;
+    s->T = snewn(s->T_nblocks * s->hlen, unsigned char);
+
+    return s;
+    } // WINSCP
+}
+
+void rfc6979_setup(RFC6979 *s, ptrlen message)
+{
+    unsigned char h1[MAX_HASH_LEN];
+    unsigned char K[MAX_HASH_LEN];
+
+    /* 3.2 (a): hash the message to get h1. */
+    ssh_hash_reset(s->hash);
+    put_datapl(s->hash, message);
+    ssh_hash_digest(s->hash, h1);
+
+    /* 3.2 (b): set V to a sequence of 0x01 bytes the same size as the
+     * hash function's output. */
+    memset(s->V, 1, s->hlen);
+
+    /* 3.2 (c): set the initial HMAC key K to all zeroes, again the
+     * same size as the hash function's output. */
+    memset(K, 0, s->hlen);
+    ssh2_mac_setkey(s->mac, make_ptrlen(K, s->hlen));
+
+    /* 3.2 (d): compute the MAC of V, the private key, and h1, with
+     * key K, making a new key to replace K. */
+    ssh2_mac_start(s->mac);
+    put_data(s->mac, s->V, s->hlen);
+    put_byte(s->mac, 0);
+    put_int2octets(s->mac, s->x, s);
+    put_bits2octets(s->mac, make_ptrlen(h1, s->hlen), s);
+    ssh2_mac_genresult(s->mac, K);
+    ssh2_mac_setkey(s->mac, make_ptrlen(K, s->hlen));
+
+    /* 3.2 (e): replace V with its HMAC using the new K. */
+    ssh2_mac_start(s->mac);
+    put_data(s->mac, s->V, s->hlen);
+    ssh2_mac_genresult(s->mac, s->V);
+
+    /* 3.2 (f): repeat step (d), only using the new K in place of the
+     * initial all-zeroes one, and with the extra byte in the middle
+     * of the MAC preimage being 1 rather than 0. */
+    ssh2_mac_start(s->mac);
+    put_data(s->mac, s->V, s->hlen);
+    put_byte(s->mac, 1);
+    put_int2octets(s->mac, s->x, s);
+    put_bits2octets(s->mac, make_ptrlen(h1, s->hlen), s);
+    ssh2_mac_genresult(s->mac, K);
+    ssh2_mac_setkey(s->mac, make_ptrlen(K, s->hlen));
+
+    /* 3.2 (g): repeat step (e), using the again-replaced K. */
+    ssh2_mac_start(s->mac);
+    put_data(s->mac, s->V, s->hlen);
+    ssh2_mac_genresult(s->mac, s->V);
+
+    smemclr(h1, sizeof(h1));
+    smemclr(K, sizeof(K));
+}
+
+RFC6979Result rfc6979_attempt(RFC6979 *s)
+{
+    RFC6979Result result;
+
+    /* 3.2 (h) 1: set T to the empty string */
+    /* 3.2 (h) 2: make lots of output by concatenating MACs of V */
+    size_t i; // WINSCP
+    for (i = 0; i < s->T_nblocks; i++) {
+        ssh2_mac_start(s->mac);
+        put_data(s->mac, s->V, s->hlen);
+        ssh2_mac_genresult(s->mac, s->V);
+        memcpy(s->T + i * s->hlen, s->V, s->hlen);
+    }
+
+    /* 3.2 (h) 3: if we have a number in [1, q-1], return it ... */
+    result.k = bits2int(make_ptrlen(s->T, s->T_nblocks * s->hlen), s);
+    result.ok = mp_hs_integer(result.k, 1) & ~mp_cmp_hs(result.k, s->q);
+
+    /*
+     * Perturb K and regenerate V ready for the next attempt.
+     *
+     * We do this unconditionally, whether or not the k we just
+     * generated is acceptable. The time cost isn't large compared to
+     * the public-key operation we're going to do next (not to mention
+     * the larger number of these same operations we've already done),
+     * and it makes side-channel testing easier if this function is
+     * constant-time from beginning to end.
+     *
+     * In other rejection-sampling situations, particularly prime
+     * generation, we're not this careful: it's enough to ensure that
+     * _successful_ attempts run in constant time, Failures can do
+     * whatever they like, on the theory that the only information
+     * they _have_ to potentially expose via side channels is
+     * information that was subsequently thrown away without being
+     * used for anything important. (Hence, for example, it's fine to
+     * have multiple different early-exit paths for failures you
+     * detect at different times.)
+     *
+     * But here, the situation is different. Prime generation attempts
+     * are independent of each other. These are not. All our
+     * iterations round this loop use the _same_ secret data set up by
+     * rfc6979_new(), and also, the perturbation step we're about to
+     * compute will be used by the next iteration if there is one. So
+     * it's absolutely _not_ true that a failed iteration deals
+     * exclusively with data that won't contribute to the eventual
+     * output. Hence, we have to be careful about the failures as well
+     * as the successes.
+     *
+     * (Even so, it would be OK to make successes and failures take
+     * different amounts of time, as long as each of those amounts was
+     * consistent. But it's easier for testing to make them the same.)
+     */
+    ssh2_mac_start(s->mac);
+    put_data(s->mac, s->V, s->hlen);
+    put_byte(s->mac, 0);
+    { // WINSCP
+    unsigned char K[MAX_HASH_LEN];
+    ssh2_mac_genresult(s->mac, K);
+    ssh2_mac_setkey(s->mac, make_ptrlen(K, s->hlen));
+    smemclr(K, sizeof(K));
+
+    ssh2_mac_start(s->mac);
+    put_data(s->mac, s->V, s->hlen);
+    ssh2_mac_genresult(s->mac, s->V);
+
+    return result;
+    } // WINSCP
+}
+
+void rfc6979_free(RFC6979 *s)
+{
+    /* We don't free s->q or s->x: our caller still owns those. */
+
+    ssh_hash_free(s->hash);
+    ssh2_mac_free(s->mac);
+    smemclr(s->T, s->T_nblocks * s->hlen);
+    sfree(s->T);
+
+    /* Clear the whole structure before freeing. Most fields aren't
+     * sensitive (pointers or well-known length values), but V is, and
+     * it's easier to clear the whole lot than fiddle about
+     * identifying the sensitive fields. */
+    smemclr(s, sizeof(*s));
+
+    sfree(s);
+}
+
+mp_int *rfc6979(
+    const ssh_hashalg *hashalg, mp_int *q, mp_int *x, ptrlen message)
+{
+    RFC6979 *s = rfc6979_new(hashalg, q, x);
+    rfc6979_setup(s, message);
+    { // WINSCP
+    RFC6979Result result;
+    while (true) {
+        result = rfc6979_attempt(s);
+        if (result.ok)
+            break;
+        else
+            mp_free(result.k);
+    }
+    rfc6979_free(s);
+    return result.k;
+    } // WINSCP
+}

+ 2 - 0
source/putty/defs.h

@@ -188,6 +188,8 @@ typedef struct ecdh_key ecdh_key;
 typedef struct ecdh_keyalg ecdh_keyalg;
 typedef struct NTRUKeyPair NTRUKeyPair;
 typedef struct NTRUEncodeSchedule NTRUEncodeSchedule;
+typedef struct RFC6979 RFC6979;
+typedef struct RFC6979Result RFC6979Result;
 
 typedef struct dlgparam dlgparam;
 typedef struct dlgcontrol dlgcontrol;

+ 17 - 4
source/putty/ssh.h

@@ -637,11 +637,18 @@ mp_int *ssh_rsakex_decrypt(
     RSAKey *key, const ssh_hashalg *h, ptrlen ciphertext);
 
 /*
- * Helper function for k generation in DSA, reused in ECDSA
+ * System for generating k in DSA and ECDSA.
  */
-mp_int *dsa_gen_k(const char *id_string,
-                  mp_int *modulus, mp_int *private_key,
-                  unsigned char *digest, int digest_len);
+struct RFC6979Result {
+    mp_int *k;
+    unsigned ok;
+};
+RFC6979 *rfc6979_new(const ssh_hashalg *hashalg, mp_int *q, mp_int *x);
+void rfc6979_setup(RFC6979 *s, ptrlen message);
+RFC6979Result rfc6979_attempt(RFC6979 *s);
+void rfc6979_free(RFC6979 *s);
+mp_int *rfc6979(const ssh_hashalg *hashalg, mp_int *modulus,
+                mp_int *private_key, ptrlen message);
 #endif
 
 struct ssh_cipher {
@@ -790,6 +797,11 @@ void nullmac_next_message(ssh2_mac *m);
  * string with a given key in the most obvious way. */
 void mac_simple(const ssh2_macalg *alg, ptrlen key, ptrlen data, void *output);
 
+/* Constructor that makes an HMAC object given just a MAC. This object
+ * will have empty 'name' and 'etm_name' fields, so it's not suitable
+ * for use in SSH. It's used as a subroutine in RFC 6979. */
+ssh2_mac *hmac_new_from_hash(const ssh_hashalg *hash);
+
 #endif // !WINSCP_VS
 
 struct ssh_hash {
@@ -1241,6 +1253,7 @@ extern const ssh2_macalg ssh_hmac_sha1_buggy;
 extern const ssh2_macalg ssh_hmac_sha1_96;
 extern const ssh2_macalg ssh_hmac_sha1_96_buggy;
 extern const ssh2_macalg ssh_hmac_sha256;
+extern const ssh2_macalg ssh_hmac_sha384;
 extern const ssh2_macalg ssh_hmac_sha512;
 extern const ssh2_macalg ssh2_poly1305;
 #endif

+ 10 - 0
source/putty/utils/memory.c

@@ -2,6 +2,12 @@
  * PuTTY's memory allocation wrappers.
  */
 
+#ifdef ALLOCATION_ALIGNMENT
+/* Before we include standard headers, define _ISOC11_SOURCE so that
+ * we get the declaration of aligned_alloc(). */
+#define _ISOC11_SOURCE
+#endif
+
 #include <assert.h>
 #include <stdlib.h>
 #include <limits.h>
@@ -31,6 +37,8 @@ void *safemalloc(size_t factor1, size_t factor2, size_t addend)
     void *p;
 #ifdef MINEFIELD
     p = minefield_c_malloc(size);
+#elif defined ALLOCATION_ALIGNMENT
+    p = aligned_alloc(ALLOCATION_ALIGNMENT, size);
 #else
     p = malloc(size);
 #endif
@@ -58,6 +66,8 @@ void *saferealloc(void *ptr, size_t n, size_t size)
         if (!ptr) {
 #ifdef MINEFIELD
             p = minefield_c_malloc(size);
+#elif defined ALLOCATION_ALIGNMENT
+            p = aligned_alloc(ALLOCATION_ALIGNMENT, size);
 #else
             p = malloc(size);
 #endif

+ 4 - 4
source/putty/version.h

@@ -1,5 +1,5 @@
 /* Generated by automated build script */
-#define RELEASE 0.80
-#define TEXTVER "Release 0.80"
-#define SSHVER "-Release-0.80"
-#define BINARY_VERSION 0,80,0,0
+#define RELEASE 0.81
+#define TEXTVER "Release 0.81"
+#define SSHVER "-Release-0.81"
+#define BINARY_VERSION 0,81,0,0

+ 1 - 1
source/resource/TextsCore1.rc

@@ -513,7 +513,7 @@ BEGIN
 
   CORE_VARIABLE_STRINGS, "CORE_VARIABLE"
   PUTTY_BASED_ON, "SSH and SCP code based on PuTTY %s"
-  PUTTY_COPYRIGHT, "Copyright © 1997–2023 Simon Tatham"
+  PUTTY_COPYRIGHT, "Copyright © 1997–2024 Simon Tatham"
   PUTTY_URL, "https://www.chiark.greenend.org.uk/~sgtatham/putty/"
   FILEZILLA_BASED_ON2, "FTP code based on FileZilla"
   FILEZILLA_COPYRIGHT2, "Copyright © Tim Kosse"

+ 125 - 21
translations/BE.ini

@@ -94,19 +94,25 @@ RemotePropertiesGroup.Caption="Параметры запампоўвання"
 RemotePropertiesGroup.PreserveRightsCheck.Caption="Задаць &дазволы"
 RemotePropertiesGroup.RightsEdit.ButtonHint="Канфігурацыя"
 RemotePropertiesGroup.IgnorePermErrorsCheck.Caption="Ігнараваць &памылкі дазволаў"
-ChangeCaseGroup.Caption="Перайменаванне файлаў"
+RemotePropertiesGroup.EncryptNewFilesCheck.Caption="&Шыфраваць новыя файлы"
+ChangeCaseGroup.Caption="Мадыфікацыя назвы файла"
 ChangeCaseGroup.CCLowerCaseShortButton.Caption="Ніжні рэгістр (&8.3)"
 ChangeCaseGroup.CCNoChangeButton.Caption="Не &змяняць"
-ChangeCaseGroup.CCUpperCaseButton.Caption="&ВЯЛІКІМІ"
-ChangeCaseGroup.CCLowerCaseButton.Caption="&малымі"
+ChangeCaseGroup.CCUpperCaseButton.Caption="&Верхні рэгістр"
+ChangeCaseGroup.CCLowerCaseButton.Caption="&Ніжні рэгістр"
 ChangeCaseGroup.ReplaceInvalidCharsCheck.Caption="&Замяніць \"\\:*?\"..."
-TransferModeGroup.Caption="Спосаб перадачы"
-TransferModeGroup.AsciiFileMaskLabel.Caption="&Гэтыя файлы лічыць тэкставымі"
+TransferModeGroup.Caption="Рэжым перадачы"
+TransferModeGroup.AsciiFileMaskLabel.Caption="&Перадаваць гэтыя файлы ў тэкставым рэжыме"
 TransferModeGroup.TMTextButton.Caption="Як &тэкст (тэкст, HTML і сцэнары)"
 TransferModeGroup.TMBinaryButton.Caption="Як &двайковыя (архівы, DOC і інш.)"
 TransferModeGroup.TMAutomaticButton.Caption="&Аўтавызначэнне"
 OtherGroup.Caption="Іншыя"
+OtherGroup.IncludeFileMaskLabel.Caption="&Маска файла:"
+OtherGroup.IncludeFileMaskButton.Caption="&Рэдагаваць..."
+OtherGroup.NewerOnlyCheck.Caption="Толькі &новыя і змененыя файлы"
 OtherGroup.IncludeFileMaskHintText.Caption="падказкі па масцы"
+OtherGroup.ExcludeHiddenFilesCheck.Caption="Выключыць &схаваныя файлы"
+OtherGroup.ExcludeEmptyDirectoriesCheck.Caption="Выключыць &пустыя каталогі"
 
 [CreateDirectory.dfm]
 Caption="Стварэнне папкі"
@@ -126,6 +132,7 @@ HelpButton.Caption="&Дапамога"
 [CustomCommand.dfm]
 Group.DescriptionLabel.Caption="&Апісанне:"
 Group.Label1.Caption="&Каманда:"
+Group.ShortCutLabel.Caption="Спалучэнне &клавіш"
 Group.ApplyToDirectoriesCheck.Caption="&Прымяніць для каталогаў"
 Group.RecursiveCheck.Caption="&Рэкурсіўна"
 Group.LocalCommandButton.Caption="&Лакальная каманда"
@@ -133,12 +140,14 @@ Group.RemoteCommandButton.Caption="&Аддаленая каманда"
 Group.ShowResultsCheck.Caption="&Выводзіць вынікі на тэрмінале"
 Group.CopyResultsCheck.Caption="&Капіяваць вынік у буфер абмену"
 Group.HintText.Caption="&Шаблоны"
+Group.RemoteFilesCheck.Caption="Выкарыстоўваць аддаленыя &файлы"
 OkButton.Caption="ОК"
 CancelButton.Caption="Скасаваць"
 HelpButton.Caption="&Дапамога"
 
 [CustomScpExplorer.dfm]
-QueueSplitter.Hint="Перацягніце, каб змяніць памер спісу. НАцісніце двойчы, каб схаваць спіс."
+QueueSplitter.Hint="Перацягніце, каб змяніць памер спісу. Націсніце двойчы, каб схаваць спіс."
+QueuePanel.QueueFileListSplitter.Hint="Перацягніце, каб змяніць памер спісу файлаў. Націсніце двойчы, каб схаваць спіс."
 QueuePanel.QueueView3.item0.Caption="Аперацыя"
 QueuePanel.QueueView3.item1.Caption="Крыніца"
 QueuePanel.QueueView3.item2.Caption="Прызначэнне"
@@ -285,6 +294,7 @@ DirectionGroup.SynchronizeLocalButton.Caption="&Лакальны"
 CompareCriterionsGroup.Caption="Крытэрыі параўнання"
 CompareCriterionsGroup.SynchronizeByTimeCheck.Caption="Час &змены"
 CompareCriterionsGroup.SynchronizeBySizeCheck.Caption="&Памер файла"
+CompareCriterionsGroup.SynchronizeCaseSensitiveCheck.Caption="З улікам &рэгістра"
 CompareCriterionsGroup.SynchronizeByChecksumCheck.Caption="Кантрольная &сума"
 SaveSettingsCheck.Caption="З&ахоўваць параметры"
 CopyParamGroup.Caption="Налады перадачы"
@@ -300,6 +310,9 @@ OkMenu.StartInNewWindowItem.Caption="Запускаць у но&вым акне"
 OptionsPageControl.UrlSheet.Caption="URL-адрас"
 OptionsPageControl.UrlSheet.UserNameCheck.Caption="&Імя карыстальніка"
 OptionsPageControl.UrlSheet.HostKeyCheck.Caption="SSH Ключ &хоста"
+OptionsPageControl.UrlSheet.WinSCPSpecificCheck.Caption="Спецыфічны для &WinSCP"
+OptionsPageControl.UrlSheet.SaveExtensionCheck.Caption="&Захоўваць пашырэнне"
+OptionsPageControl.UrlSheet.RemoteDirectoryCheck.Caption="Пачатковы &каталог"
 OptionsPageControl.UrlSheet.PasswordCheck.Caption="&Пароль"
 OptionsPageControl.UrlSheet.RawSettingsCheck.Caption="Па&шыраныя налады"
 OptionsPageControl.ScriptSheet.Caption="Сцэнарый"
@@ -395,6 +408,8 @@ ActionList.PasteUrlAction.Caption="Уставіць &URL-адрас сеанса
 ManageSitePopupMenu.Options1.Caption="Параметры"
 ManageFolderPopupMenu.Options3.Caption="Параметры"
 ManageNewSitePopupMenu.Options2.Caption="Параметры"
+ManageWorkspacePopupMenu.MenuItem2.Caption="Працоўная прастора"
+ManageWorkspacePopupMenu.MenuItem18.Caption="Значок абалонкі працоўнай прасторы"
 ManageWorkspacePopupMenu.Options4.Caption="Параметры"
 SessionAdvancedPopupMenu.Session1.Caption="Сеанс"
 SessionAdvancedPopupMenu.MenuItem14.Caption="Глабальныя параметры"
@@ -502,6 +517,8 @@ ExplorerActions.ExplorerUpdatesBandAction.Caption="Кнопка &абнаўле
 ExplorerActions.ExplorerUpdatesBandAction.Hint="Паказаць/схаваць понель абнаўленняў"
 ExplorerActions.ExplorerTransferBandAction.Caption="Налады &перадачы"
 ExplorerActions.ExplorerTransferBandAction.Hint="Паказаць/схаваць панэль налад перадачы"
+ExplorerActions.WorkspacesAction.Caption="&Працоўныя прасторы"
+ExplorerActions.WorkspacesAction.Hint="Адкрыць працоўную прастору"
 ExplorerActions.PreferencesAction.Caption="&Налады..."
 ExplorerActions.PreferencesAction.Hint="Налады|Паказаць/змяніць налады праграмы"
 ExplorerActions.RemoteChangePathAction2.Caption="Змяніць &каталог"
@@ -730,6 +747,8 @@ ExplorerActions.RemoteFindFilesAction2.Caption="По&шук"
 ExplorerActions.RemoteFindFilesAction2.Hint="Пошук файлаў|Пошук файлаў і каталогаў"
 ExplorerActions.CurrentEditInternalAction.Caption="&Унутраны рэдактар"
 ExplorerActions.CurrentEditInternalAction.Hint="Рэдагаваць (унутраным)|Рэдагаваць выбраны(-я) файл(-ы) пры дапамозе ўнутранага рэдактара"
+ExplorerActions.SaveWorkspaceAction.Caption="Захаваць &працоўную прастору"
+ExplorerActions.SaveWorkspaceAction.Hint="Захаваць працоўную прастору|Захаваць працоўную прастору"
 ExplorerActions.LocalRenameAction2.Caption="&Перайменаваць"
 ExplorerActions.LocalRenameAction2.Hint="Перайменаваць|Перайменаваць выбраны файл"
 ExplorerActions.LocalEditAction2.Caption="&Рэдагаваць"
@@ -1032,6 +1051,13 @@ MainPanel.PageControl.WindowSheet.PathInCaptionGroup.Caption="Шлях ў заг
 MainPanel.PageControl.WindowSheet.PathInCaptionGroup.PathInCaptionFullButton.Caption="&Паказваць поўны шлях"
 MainPanel.PageControl.WindowSheet.PathInCaptionGroup.PathInCaptionShortButton.Caption="&Паказваць кароткі шлях"
 MainPanel.PageControl.WindowSheet.PathInCaptionGroup.PathInCaptionNoneButton.Caption="&Не паказваць"
+MainPanel.PageControl.WindowSheet.WorkspacesGroup.Caption="Працоўныя прасторы"
+MainPanel.PageControl.WindowSheet.WorkspacesGroup.AutoWorkspaceLabel.Caption="Назва прад&вызначанай працоўнай прасторы:"
+MainPanel.PageControl.WindowSheet.WorkspacesGroup.AutoSaveWorkspaceCheck.Caption="&Аўтаматычна захоўваць працоўную прастору пры выхадзе"
+MainPanel.PageControl.SecuritySheet.MasterPasswordGroup.Caption="Галоўны пароль"
+MainPanel.PageControl.SecuritySheet.MasterPasswordGroup.SetMasterPasswordButton.Caption="&Змяніць галоўны пароль..."
+MainPanel.PageControl.SecuritySheet.MasterPasswordGroup.UseMasterPasswordCheck.Caption="&Выкарыстоўваць галоўны пароль..."
+MainPanel.PageControl.SecuritySheet.PasswordGroupBox.Caption="Пароль сеанса"
 MainPanel.PageControl.SecuritySheet.SshHostCAsGroup.AddSshHostCAButton.Caption="&Дадаць..."
 MainPanel.PageControl.SecuritySheet.SshHostCAsGroup.RemoveSshHostCAButton.Caption="&Выдаліць"
 MainPanel.PageControl.SecuritySheet.SshHostCAsGroup.EditSshHostCAButton.Caption="&Рэдагаваць..."
@@ -1265,11 +1291,11 @@ MainPanel.PageControl.DirectoriesSheet.DirectoryOptionsGroup.CacheDirectoriesChe
 MainPanel.PageControl.DirectoriesSheet.DirectoryOptionsGroup.CacheDirectoryChangesCheck.Caption="Кэ&шаваць змены каталога"
 MainPanel.PageControl.DirectoriesSheet.DirectoryOptionsGroup.ResolveSymlinksCheck.Caption="&Дазваляць знакавыя спасылкі"
 MainPanel.PageControl.DirectoriesSheet.DirectoryOptionsGroup.PreserveDirectoryChangesCheck.Caption="Сталы кэш"
-MainPanel.PageControl.RecycleBinSheet.Caption="Кошык"
-MainPanel.PageControl.RecycleBinSheet.RecycleBinGroup.Caption="Кошык"
-MainPanel.PageControl.RecycleBinSheet.RecycleBinGroup.RecycleBinPathLabel.Caption="&Кошык на серверы:"
-MainPanel.PageControl.RecycleBinSheet.RecycleBinGroup.DeleteToRecycleBinCheck.Caption="&Перасоўваць выдаляныя файлы ў кошык"
-MainPanel.PageControl.RecycleBinSheet.RecycleBinGroup.OverwrittenToRecycleBinCheck.Caption="Перасоўваць выдаляныя з &сервера файлы ў кошык (толькі на SFTP)"
+MainPanel.PageControl.RecycleBinSheet.Caption="Сметніца"
+MainPanel.PageControl.RecycleBinSheet.RecycleBinGroup.Caption="Сметніца"
+MainPanel.PageControl.RecycleBinSheet.RecycleBinGroup.RecycleBinPathLabel.Caption="&Аддаленая сметніца:"
+MainPanel.PageControl.RecycleBinSheet.RecycleBinGroup.DeleteToRecycleBinCheck.Caption="&Перамяшчаць выдаленыя файлы ў сметніцу"
+MainPanel.PageControl.RecycleBinSheet.RecycleBinGroup.OverwrittenToRecycleBinCheck.Caption="Перамяшчаць перазапісаныя &аддаленыя файлы ў сметніцу (SFTP)"
 MainPanel.PageControl.EncryptionSheet.Caption="Шыфраванне"
 MainPanel.PageControl.EncryptionSheet.EncryptFilesCheck.Caption="&Шыфраваць файлы"
 MainPanel.PageControl.EncryptionSheet.EncryptFilesGroup.Caption="Параметры шыфравання"
@@ -1303,19 +1329,22 @@ MainPanel.PageControl.FtpSheet.Caption="FTP"
 MainPanel.PageControl.FtpSheet.FtpGroup.Caption="Параметры пратакола"
 MainPanel.PageControl.FtpSheet.FtpGroup.FtpAccountLabel.Caption="&Уліковы запіс:"
 MainPanel.PageControl.S3Sheet.S3Group.Caption="Параметры пратакола"
+MainPanel.PageControl.S3Sheet.S3Group.Label27.Caption="&Прадвызначаны рэгіён:"
+MainPanel.PageControl.S3Sheet.S3Group.S3UrlStyleLabel.Caption="Стыль &URL:"
+MainPanel.PageControl.S3Sheet.S3Group.S3UrlStyleCombo.Items.Strings.1="Шлях"
 MainPanel.PageControl.S3Sheet.S3AuthenticationGroup.Caption="Аўтэнтыфікацыя"
 MainPanel.PageControl.WebDavSheet.WebdavGroup.Caption="Параметры пратакола"
-MainPanel.PageControl.ConnSheet.Caption="Падлучэнне"
+MainPanel.PageControl.ConnSheet.Caption="Злучэнне"
 MainPanel.PageControl.ConnSheet.FtpPingGroup.Caption="Імітаваць актыўнасць"
 MainPanel.PageControl.ConnSheet.FtpPingGroup.FtpPingIntervalLabel.Caption="&Секунд паміж пакетамі"
-MainPanel.PageControl.ConnSheet.FtpPingGroup.FtpPingOffButton.Caption="&Не"
+MainPanel.PageControl.ConnSheet.FtpPingGroup.FtpPingOffButton.Caption="&Выкл"
 MainPanel.PageControl.ConnSheet.FtpPingGroup.FtpPingDummyCommandButton.Caption="Пустыя &каманды пратакола"
 MainPanel.PageControl.ConnSheet.TimeoutGroup.Caption="Час чакання"
 MainPanel.PageControl.ConnSheet.TimeoutGroup.Label11.Caption="Чаканне &адказу сервера"
 MainPanel.PageControl.ConnSheet.TimeoutGroup.Label12.Caption="с."
 MainPanel.PageControl.ConnSheet.PingGroup.Caption="Імітаваць актыўнасць"
 MainPanel.PageControl.ConnSheet.PingGroup.PingIntervalLabel.Caption="&Секунд паміж пакетамі"
-MainPanel.PageControl.ConnSheet.PingGroup.PingOffButton.Caption="&Не"
+MainPanel.PageControl.ConnSheet.PingGroup.PingOffButton.Caption="&Выкл"
 MainPanel.PageControl.ConnSheet.PingGroup.PingNullPacketButton.Caption="Пустыя &пакеты SSH"
 MainPanel.PageControl.ConnSheet.PingGroup.PingDummyCommandButton.Caption="Пустыя &каманды пратакола"
 MainPanel.PageControl.ConnSheet.IPvGroup.Caption="Версія IP"
@@ -1323,6 +1352,7 @@ MainPanel.PageControl.ConnSheet.IPvGroup.IPAutoButton.Caption="&Аўта"
 MainPanel.PageControl.ConnSheet.IPvGroup.IPv4Button.Caption="IPv&4"
 MainPanel.PageControl.ConnSheet.IPvGroup.IPv6Button.Caption="IPv&6"
 MainPanel.PageControl.ConnSheet.ConnectionGroup.Caption="Злучэнне"
+MainPanel.PageControl.ConnSheet.ConnectionGroup.FtpPasvModeCheck.Caption="&Пасіўны рэжым"
 MainPanel.PageControl.ProxySheet.Caption="Проксі"
 MainPanel.PageControl.ProxySheet.ProxyTypeGroup.Caption="Проксі"
 MainPanel.PageControl.ProxySheet.ProxyTypeGroup.ProxyMethodLabel.Caption="&Тып проксі:"
@@ -1344,9 +1374,18 @@ MainPanel.PageControl.TunnelSheet.TunnelSessionGroup.Label6.Caption="&Хост:"
 MainPanel.PageControl.TunnelSheet.TunnelSessionGroup.Label14.Caption="&Порт:"
 MainPanel.PageControl.TunnelSheet.TunnelSessionGroup.Label15.Caption="&Імя карыстальніка:"
 MainPanel.PageControl.TunnelSheet.TunnelSessionGroup.Label16.Caption="П&ароль:"
+MainPanel.PageControl.TunnelSheet.TunnelOptionsGroup.TunnelLocalPortNumberEdit.Items.Strings.0="Аўтавыбар"
 MainPanel.PageControl.TunnelSheet.TunnelAuthenticationParamsGroup.Label18.Caption="Прыватны &ключ"
 MainPanel.PageControl.TunnelSheet.TunnelAuthenticationParamsGroup.TunnelPrivateKeyEdit3.Filter="Файлы прыватных ключоў PuTTY (*.ppk)|*.ppk|Усе файлы прыватных ключоў (*.ppk;*.pem;*.key;id_*)|*.ppk;*.pem;*.key;id_*|Усе файлы (*.*)|*.*"
 MainPanel.PageControl.TunnelSheet.TunnelAuthenticationParamsGroup.TunnelPrivateKeyEdit3.DialogTitle="Выберыце прыватны ключ"
+MainPanel.PageControl.SslSheet.TlsGroup.Caption="Параметры TLS"
+MainPanel.PageControl.SslSheet.TlsGroup.MinTlsVersionLabel.Caption="Мі&німальная версія TLS:"
+MainPanel.PageControl.SslSheet.TlsGroup.MaxTlsVersionLabel.Caption="Ма&ксімальная версія TLS:"
+MainPanel.PageControl.SslSheet.TlsGroup.SslSessionReuseCheck2.Caption="&Паўторна выкарыстоўваць ID сеанса TLS для злучэнняў перадачы даных"
+MainPanel.PageControl.SslSheet.TlsAuthenticationGroup.Caption="Параметры аўтэнтыфікацыі"
+MainPanel.PageControl.SslSheet.TlsAuthenticationGroup.Label4.Caption="Сертыфікат &кліента:"
+MainPanel.PageControl.SslSheet.TlsAuthenticationGroup.TlsCertificateFileEdit.Filter="Сертыфікаты і файлы прыватных ключоў (*.pfx;*.p12;*.key;*.pem)|*.pfx;*.p12;*.key;*.pem|Усе файлы(*.*)|*.*"
+MainPanel.PageControl.SslSheet.TlsAuthenticationGroup.TlsCertificateFileEdit.DialogTitle="Выберыце файл сертыфіката кліента"
 MainPanel.PageControl.AdvancedSheet.Caption="SSH"
 MainPanel.PageControl.AdvancedSheet.ProtocolGroup.Caption="Параметры пратакола"
 MainPanel.PageControl.AdvancedSheet.ProtocolGroup.CompressionCheck.Caption="Уключыць &сцісканне"
@@ -1360,7 +1399,8 @@ MainPanel.PageControl.KexSheet.KexOptionsGroup.Caption="Параметры ал
 MainPanel.PageControl.KexSheet.KexOptionsGroup.Label28.Caption="&Палітыка выбару алгарытму:"
 MainPanel.PageControl.KexSheet.KexOptionsGroup.KexUpButton.Caption="&Уверх"
 MainPanel.PageControl.KexSheet.KexOptionsGroup.KexDownButton.Caption="&Уніз"
-MainPanel.PageControl.KexSheet.KexReexchangeGroup.Caption="Параметры пераабрання ключоў"
+MainPanel.PageControl.KexSheet.KexOptionsGroup.AuthGSSAPIKEXCheck.Caption="Спрабаваць абмен ключамі &GSSAPI"
+MainPanel.PageControl.KexSheet.KexReexchangeGroup.Caption="Параметры пераабмену ключоў"
 MainPanel.PageControl.KexSheet.KexReexchangeGroup.Label31.Caption="Менять не реже, мин (0 - не менять)"
 MainPanel.PageControl.KexSheet.KexReexchangeGroup.Label32.Caption="Менять после пересылки объёма (0 - не менять)"
 MainPanel.PageControl.AuthSheet.Caption="Аўтэнтыфікацыя"
@@ -1594,6 +1634,7 @@ EXECUTE_APP_ERROR="Не магу выканаць \"%s\"."
 FILE_NOT_FOUND="Файл \"%s\" не знойдзены."
 DOCUMENT_WAIT_ERROR="Не магу дачакацца закрыцця дакумента."
 DELETE_LOCAL_FILE_ERROR="Памылка выдалення файла \"%s\"."
+CANNOT_OPEN_SESSION_FOLDER="Не ўдалося адкрыць папку сеанса або працоўную прастору."
 TOO_MANY_REDIRECTS="Зашмат перанакіроўванняў"
 UNKNOWN_CHECKSUM="Алгарытм кантрольнай сумы \"%s\" не падтрымліваецца"
 S3_ERROR_RESOURCE="Рэсурс: %s"
@@ -1614,6 +1655,7 @@ PASSPHRASE_TITLE="Увядзіце сакрэтную фразу ключа"
 SERVER_PROMPT_TITLE="Запрашэнне сервера"
 USERNAME_TITLE="Імя карыстальніка"
 USERNAME_PROMPT2="&Імя карыстальніка:"
+SERVER_PROMPT_TITLE2="Падказка сервера: %s"
 NEW_PASSWORD_TITLE="Новы пароль"
 PROMPT_PROMPT="&Адказ:"
 PASSWORD_PROMPT="&Пароль: "
@@ -1792,6 +1834,11 @@ REMOVE_PATH_ERROR="Памылка выдалення шляху \"%s\" са зм
 COPY_PARAM_NO_RULE="Вы не паказалі ніводнай маскі для аўтаматычнага выбару варыянту"
 COPY_PARAM_DUPLICATE="Варыянт з апісаннем \"%s\" ужо існуе."
 CUSTOM_COMMAND_AD_HOC_NAME="Ad Hoc"
+MASTER_PASSWORD_INCORRECT="Уведзены бягучы галоўны пароль няправільны"
+MASTER_PASSWORD_DIFFERENT="Новы галоўны пароль не супадае з уведзеным паўторна"
+CONSOLE_MASTER_PASSWORD_PROMPT="Галоўны пароль"
+WORKSPACE_NOT_FOLDER="\"%s\" - гэта працоўная прастора, а не папка сеанса."
+FOLDER_NOT_WORKSPACE="\"%s\" - гэта папка сеанса, а не працоўная прастора."
 STACK_TRACE="Трасіроўка стэка"
 TIPS_NONE="Няма падказак."
 FINGERPRINTSCAN_NEED_SECURE_SESSION="Бяспечны сеанс (SSH або TLS/SSL) не вызначаны."
@@ -1818,13 +1865,17 @@ PENDING_EDITORS="Дагэтуль адкрыты некаторыя файлы.
 OPEN_BUTTON="&Адкрыць"
 NEVER_SHOW_AGAIN="&Больш не паказваць"
 CONFIRM_ADD_SEARCH_PATH="Хочаце дадаць шлях праграмы \"%s\" да сістэмнага шляху пошуку (%%PATH%%)?"
-CONFIRM_DELETE_SESSION="Вы ўпэўнены, што хочаце выдаліць злучэнне \"%s\"?"
+CONFIRM_DELETE_SESSION="Вы ўпэўнены, што хочаце выдаліць \"%s\"?"
 TOO_MANY_WATCH_DIRECTORIES="Знойдзена больш за %d каталогаў і падкаталогаў. Сачэнне за вялікай колькасцю каталогаў можа рэзка зменшыць прадукцыйнасць камп'ютара.\n \nВы хочаце праглядзець яшчэ да %d каталогаў?"
 TIMEOUT_BUTTON="%s (%d с.)"
 ADD_BOOKMARK_SHARED="Дадаць у &абагуленыя закладкі"
+CONFIRM_CREATE_SHORTCUT_WORKSPACE="Стварыць ярлык на працоўным стале для працоўнай прасторы \"%s\"?"
+CLOSE_SESSIONS_WORKSPACE3="Скончыць усе сеансы і закрыць праграму, не захоўваючы працоўную прастору"
 ALL_BUTTON="&Усё"
 YES_TO_ALL_BUTTON="Так для &ўсіх"
 CLOSE_BUTTON="Закрыць"
+CLOSE_SESSION_WORKSPACE="Скончыць сеанс \"%s\" і закрыць праграму, не захоўваючы працоўную прастору?"
+CLOSE_WORKSPACE="Закрыць праграму, не захоўваючы працоўную прастору?"
 COMPARE_NO_DIFFERENCES="Не знойдзена адрозненняў."
 SHORTCUT_INFO_TIP="Адкрывае злучэнне \"%s\"\n%s"
 SKIP_BUTTON="&Прапусціць"
@@ -1872,7 +1923,15 @@ OPEN_EDITOR_NAME="&Звязаная праграмы"
 AUTO_SWITCH_ON="Укл"
 AUTO_SWITCH_OFF="Выкл"
 AUTO_SWITCH_AUTO="Аўта"
+MASTER_PASSWORD_SET2="**Гадлўны пароль зададзены.**\n\nВагы захаваныя паролі цяпер абароненыя шыфрам AES."
+MASTER_PASSWORD_CHANGED="Галоўны пароль паспяхова зменены."
+MASTER_PASSWORD_CLEARED2="**Вы выдалілі галоўны пароль.**\n\nВашы захаваныя паролі цяпер не абароненыя."
 UPDATE_NEXT="Наступны раз праверым: %s"
+SHORTCUT_INFO_TIP_WORKSPACE="Адкрывае працоўнуюу пратору \"%s\""
+JUMPLIST_WORKSPACES="Нядаўнія працоўныя прасторы"
+NEW_WORKSPACE="Мая працоўная прастора"
+AUTO_WORKSPACE="Працоўная прастора \"%s\" будзе захоўвацца аўтаматычна."
+WORKSPACE_INFO_TIP="Працоўная прастора: %s"
 MESSAGE_LOADING="Загрузка..."
 NEW_VERSION_CLICK="%s\n\nНацісніце тут, каб даведацца, што новага."
 USAGE_NEWINSTANCE="Адкрываць новы сеанс у новым акне, нават калі %APP% ужо запушчана."
@@ -1880,13 +1939,17 @@ USAGE_EDIT="Адкрывае аддалены файл ва ўнутраным 
 USAGE_SYNCHRONIZE="Сінхранізуе змесціва двух каталогаў"
 USAGE_INI="Шлях да INI файла канфігурацыі"
 USAGE_PRIVATEKEY="Файл прыватнага ключа SSH."
+AUTO_WORKSPACE_ENABLE="Націсніце \"Не\", каб уключыць аўтазахаванне працоўнай прасторы"
+PUTTY_SETTINGS_SITE_NAME="Налады тэрмінала для %s"
+QUEUE_DELETING="Выдаленне..."
+STORE_MIGRATION_THANKS="Дзякуй, што купілі WinSCP."
 UPDATE_CURRENT="Ваша версія: %s"
 COPY_FILE="%s файл \"%s\" у %s"
 COPY_FILES="%s %d файлаў у %s"
 COPY_TOLOCAL="лакальны каталог"
 COPY_TOREMOTE="аддалены каталог"
 COPY_TODROP="паказанае месца"
-COPY_MOVE_CAPTION="Перанесці"
+COPY_MOVE_CAPTION="Перамясціць"
 PROGRESS_SETPROPERTIES="Уласцівасці злучэння"
 PROGRESS_DELETE="Выдаленне"
 PROGRESS_TEMP_DIR="Часовы каталог"
@@ -1990,20 +2053,42 @@ STATUS_SECURE="Бяспечнае злучэнне (%s)"
 SPECIAL_FOLDER_MY_DOCUMENTS="Мае дакументы"
 SPECIAL_FOLDER_DESKTOP="Працоўны стол"
 COMMAND_LINE_LABEL="Каманда"
+LOGIN_DUPLICATE_SESSION_FOLDER_WORKSPACE="Папка сеанса або працоўная прастора \"%s\" ужо існуе."
 LOGIN_NEW_SESSION_FOLDER_PROMPT="Назва новай папкі:"
 REMOTE_COPY_COMMAND_SESSION_FILES_DIRECTORIES="файл(-ы)|каталог(-і)"
 EDITOR_AD_HOC="Рэдактар"
 FILE_INFO_HIDDEN2="%s схаваны"
 FILTER_MASK_CAPTION="Фільтр"
+COPY_BACKGROUND="Перадаваць у &фоне"
+COPY_QUEUE="%s (дадаць у чаргу перадачы)"
+SHORTCUT_NONE="Няма"
+SHORTCUT_CAPTION="Выберыце спалучэнне клавіш"
+SHORTCUT_LABEL="Спалучэнне &клавіш"
 PREFERENCES_RECONNECT_TIMEOUT_UNLIMITED="Неабмежавана"
+MASTER_PASSWORD_CAPTION="Галоўны пароль"
+MASTER_PASSWORD_CURRENT="&Бягучы галоўны пароль:"
+MASTER_PASSWORD_NEW="&Новы галоўны пароль:"
+MASTER_PASSWORD_CONFIRM="&Увядзіце галоўны пароль яшчэ раз:"
+SAVE_SESSION_PASSWORD_MASTER="Захаваць &пароль (абаронена галоўным паролям)"
+FIND_FILE_TITLE="Пошук"
+FIND_FILE_FINDING="Пошук..."
+FIND_FILE_IN_DIRECTORY="Пошук у %s"
 FIND_FILE_DONE="Гатова."
 FIND_FILE_ABORTED="Перарвана."
 FIND_FILE_START="&Пачаць"
-FIND_FILE_STOP="&Стоп"
+FIND_FILE_STOP="&Спыніць"
 SAVE_SESSION_PASSWORD_RECOMMENDED="&Захаваць пароль"
 AUTH_CANCELLING="Скасаванне..."
 EDITOR_ENCODING_STATUS="Кадзіроўка: %s"
+LOGIN_DELETE_WORKSPACE="Вы ўпэўненыя, што хочаце выдаліць працоўную прастору \"%s\" з колькасцю сеансаў: %d?"
+SAVE_WORKSPACE_CAPTION="Захаваць працоўную прастору як"
+SAVE_WORKSPACE_PROMPT="&Захаваць працоўную прастору як"
+SAVE_WORKSPACE_PASSWORDS="Захоўваць &паролі (не рэкамендуецца)"
+SAVE_WORKSPACE_PASSWORDS_MASTER="Захоўваць &паролі (абаронена галоўным паролям)"
+SAVE_WORKSPACE_PASSWORDS_RECOMMENDED="Захоўваць &паролі"
+WORKSPACE_HINT="Адкрыць працоўную прастору \"%s\" (зацісніце Shift, каб адкрыць працоўную прастору ў новым акне)"
 SAVE_SITE_WORKSPACE_SHORTCUT="Стварыць ярлык на працоўным стале"
+SAVE_WORKSPACE_AUTO="&Аўтаматычна захоўваць гэту працоўную прастору"
 COPY_COPY_TOLOCAL="Спампаваць"
 COPY_COPY_TOREMOTE="Запампаваць"
 COPY_COPY_TOLOCAL_CAPTION="Спампаваць"
@@ -2014,9 +2099,11 @@ COPY_MOVE_TOLOCAL_CAPTION="Спампаваць і выдаліць"
 COPY_MOVE_TOREMOTE_CAPTION="Запампаваць і выдаліць"
 QUEUE_CAPTION="Чарга"
 INC_SEARCH="Пошук: %s"
+INC_NEXT_SEARCH="(Tab - наступны)"
 PROGRESS_UPLOAD="Запампоўванне"
 PROGRESS_DOWNLOAD="Спампоўванне"
 PROGRESS_IN_QUEUE="%d у чарзе"
+LOGIN_WORKSPACE_CAPTION="Працоўная прастора"
 SAVE_SESSION_FOLDER="&Папка:"
 SAVE_SESSION_ROOT_FOLDER2="<няма>"
 LOGIN_SHELL_PAGE="Абалонка"
@@ -2025,12 +2112,16 @@ EDITOR_CAPTION="Рэдактар"
 COLOR_DEFAULT_HINT="Скінуць колер сеанса (панэлі) да прадвызначанага сістэмай"
 COLOR_PICK_CAPTION="&Больш колераў"
 COLOR_PICK_HINT="Выберыце колер сеанса (панэлі)"
+COPY_PARAM_REMOVE_BOM_EOF="&Выдаляць знакі BOM і EOF"
+COPY_PARAM_REMOVE_BOM="&Выдаляць знак BOM"
 ABOUT_THIRDPARTY_HEADER="Ліцэнзійныя пагадненні ўсіх наступных праграм (бібліятэк) з'яўляюцца часткай ліцэнзійнага пагаднення праграмы."
 ABOUT_THIRDPARTY_LICENSE="Паказаць ліцэнзію"
-EDITOR_SAVING="Захаванне..."
+EDITOR_SAVING="Захоўванне..."
 ABOUT_TRANSLATIONS_HEADER="Пераклад на беларускую мову:"
 PROGRESS_FILE_LABEL="Файл:"
 PROGRESS_PATH_LABEL="Шлях:"
+PROGRESS_LOCK="Блакіраванне"
+PROGRESS_UNLOCK="Разблакіраванне"
 COLOR_TRUE_DEFAULT_CAPTION="&Прадвызначаны"
 EDITOR_BACKGROUND_COLOR_HINT="Скінуць колер рэдактара да прадвызначанага"
 EDITOR_BACKGROUND_COLOR_PICK_HINT="Выберыце колер рэдактара"
@@ -2060,11 +2151,14 @@ USAGE_COPY="&Капіяваць"
 IMPORT_KNOWNHOSTS_INFO_TIP="Хост: %s\nКлюч хоста: %s"
 EDIT_COPY="&Капіяваць"
 EDIT_SELECT_ALL="Выбраць &усё"
+EXTENSION_SHORTCUT="Спалучэнне &клавіш:"
 LOGIN_PUBLIC_KEY_FILTER="Файлы прыватных ключоў PuTTY (*.ppk)|*.ppk|Усе файлы прыватных ключоў (*.ppk;*.pem;*.key;id_*)|*.ppk;*.pem;*.key;id_*|Усе файлы (*.*)|*.*"
 EDITOR_READONLY="Толькі для чытання"
 RENAME_TAB_TITLE="Перайменаваць укладку"
 RENAME_TAB_PROMPT="Назва &новай укладкі"
 FILE_COLOR_CAPTION="Колер файла"
+UPDATES_DONATE_HTML="Каб уключыць аўтаматычныя абнаўленні, <a href=\"%DONATE_URL%\">ахвяруйце на распрацоўку WinSCP</a> або %GET_IMG% WinSCP з <a href=\"%STORE_URL%\">Microsoft Store</a>."
+EDITOR_NEW="Новы"
 PREFERENCES_STATISTICS_CAPTION="Статыстыка"
 SITE_RAW_ADD="&Дадаць..."
 LOCAL_MENU_CAPTION="&Лакальна"
@@ -2075,6 +2169,12 @@ COPY_LOCAL_COPY_CAPTION="Капіяваць"
 COPY_LOCAL_MOVE_CAPTION="Перамясціць"
 LOGIN_OPEN="Адкрыць"
 LOGIN_LOGIN="&Увайсці"
+PROPERTIES_ACL="ACL:"
+PROPERTIES_S3_USERS="&Карыстальнікі"
+PROPERTIES_S3_EVERYONE="&Усе"
+PROPERTIES_S3_R_ACL_HINT="Прачытаць ACL"
+PROPERTIES_S3_W_ACL_HINT="Запісаць ACL"
+LOGIN_S3_GENERAL_CREDENTIALS="Агульныя"
 CIPHER_NAME_WARN="-- Папярэджваць пра слабы шыфр --"
 KEX_NAME_WARN="-- Папярэджваць пра слабы шыфр --"
 KEX_NAME_DHGEX="Diffie-Hellman group exchange"
@@ -2134,7 +2234,7 @@ Vcl_Consts_SMsgDlgAbort="&Перапыніць"
 Vcl_Consts_SMsgDlgAll="&Усё"
 Vcl_Consts_SMsgDlgCancel="Скасаваць"
 Vcl_Consts_SMsgDlgClose="&Закрыць"
-Vcl_Consts_SMsgDlgConfirm="Пацвердзіць"
+Vcl_Consts_SMsgDlgConfirm="Пацверджанне"
 Vcl_Consts_SMsgDlgError="Памылка"
 Vcl_Consts_SMsgDlgHelp="&Дапамога"
 Vcl_Consts_SMsgDlgIgnore="&Ігнараваць"
@@ -2420,7 +2520,7 @@ AcceptButton="&Прыняць"
 
 [Extensions.rc]
 GenerateHttpUrl.Name="Згенерыраваць &HTTP URL"
-GenerateHttpUrl.Description="Згенерырet HTTP URL-адрас выбранага файла"
+GenerateHttpUrl.Description="Згенерыруе HTTP URL-адрас выбранага файла"
 GenerateHttpUrl.URLGroup.Caption="URL-адрас"
 GenerateHttpUrl.HttpsCheckbox.Caption="Выкарыстоўваць HTTP&S"
 GenerateHttpUrl.OptionsGroup.Caption="Параметры"
@@ -2429,9 +2529,13 @@ GenerateHttpUrl.ClipboardCheckbox.Caption="Капіяваць URL-адрас у
 GenerateHttpUrl.OpenCheckbox.Caption="Адкрыць URL-адрас у браўзеры"
 CompareFiles.Name="&Параўнаць файлы"
 CompareFiles.OptionsGroup.Caption="Параметры"
+CompareFiles.ToolDropdownlist.1="Аўта"
 SearchText.TextTextbox.Caption="Тэкст:"
 SearchText.WildcardTextbox.Caption="Маска файла:"
+ZipUpload.ArchiveNameTextbox.Caption="Назва &архіва:"
 ZipUpload.Use7zipCheckbox.Caption="Выкарыстоўваць &7-zip"
+ZipUpload.Archive7zipDropdownlist.Caption="&Тып архіва (з 7-zip):"
+ZipUpload.Path7zipFile.Caption="&Шлях да 7-zip (7z.exe/7za.exe):"
 ZipUpload.LoggingGroup.Caption="Журнал"
 KeepLocalUpToDate.DirectoriesGroup.Caption="Каталогі"
 KeepLocalUpToDate.OptionsGroup.Caption="Параметры"

+ 1 - 1
translations/version.ini

@@ -41,7 +41,7 @@ SRL=45
 HR=37
 GL=4
 FA=52
-BE=11
+BE=12
 FP=15
 EU=0
 ID=28