mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-23 08:47:47 +02:00
Use encryption for bypass
This commit is contained in:
@@ -458,6 +458,12 @@ The value of ``bypass`` is of the form ``hash_function_name : hash_value``
|
|||||||
they will introduce a lot of latency to starting a session and in any case
|
they will introduce a lot of latency to starting a session and in any case
|
||||||
there is no mathematical proof that **any** hash function is not brute-forceable.
|
there is no mathematical proof that **any** hash function is not brute-forceable.
|
||||||
|
|
||||||
|
Terminal implementations are free to use their own more advanced hashing
|
||||||
|
schemes, with prefixes other than those starting with ``sha``, which are
|
||||||
|
reserved. For instance, kitty uses a scheme based on public key encryption
|
||||||
|
via :envvar:`KITTY_PUBLIC_KEY`. For details of this scheme, see the
|
||||||
|
``check_bypass()`` function in the kitty source code.
|
||||||
|
|
||||||
Encoding of transfer commands as escape codes
|
Encoding of transfer commands as escape codes
|
||||||
------------------------------------------------
|
------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -361,7 +361,13 @@ func (self *SendManager) start_transfer() string {
|
|||||||
|
|
||||||
func (self *SendManager) initialize() {
|
func (self *SendManager) initialize() {
|
||||||
if self.bypass != "" {
|
if self.bypass != "" {
|
||||||
self.bypass = encode_bypass(self.request_id, self.bypass)
|
q, err := encode_bypass(self.request_id, self.bypass)
|
||||||
|
if err == nil {
|
||||||
|
self.bypass = q
|
||||||
|
} else {
|
||||||
|
fmt.Fprintln(os.Stderr, "Ignoring password because of error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
self.fid_map = make(map[string]*File, len(self.files))
|
self.fid_map = make(map[string]*File, len(self.files))
|
||||||
for _, f := range self.files {
|
for _, f := range self.files {
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ package transfer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"kitty/tools/crypto"
|
||||||
"kitty/tools/utils"
|
"kitty/tools/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,10 +33,20 @@ func home_path() string {
|
|||||||
return global_home
|
return global_home
|
||||||
}
|
}
|
||||||
|
|
||||||
func encode_bypass(request_id string, bypass string) string {
|
func encode_bypass(request_id string, bypass string) (string, error) {
|
||||||
q := request_id + ";" + bypass
|
q := request_id + ";" + bypass
|
||||||
sum := sha256.Sum256(utils.UnsafeStringToBytes(q))
|
if pkey_encoded := os.Getenv("KITTY_PUBLIC_KEY"); pkey_encoded != "" {
|
||||||
return fmt.Sprintf("sha256:%x", sum)
|
encryption_protocol, pubkey, err := crypto.DecodePublicKey(pkey_encoded)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
encrypted, err := crypto.Encrypt_data(utils.UnsafeStringToBytes(q), pubkey, encryption_protocol)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("kitty-1:%s", utils.UnsafeBytesToString(encrypted)), nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("KITTY_PUBLIC_KEY env var not set, cannot transmit password securely")
|
||||||
}
|
}
|
||||||
|
|
||||||
func abspath(path string, use_home ...bool) string {
|
func abspath(path string, use_home ...bool) string {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import base64
|
||||||
import errno
|
import errno
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import stat
|
import stat
|
||||||
import tempfile
|
import tempfile
|
||||||
from base64 import standard_b64decode
|
|
||||||
from collections import defaultdict, deque
|
from collections import defaultdict, deque
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from dataclasses import Field, dataclass, field, fields
|
from dataclasses import Field, dataclass, field, fields
|
||||||
@@ -14,12 +15,12 @@ from enum import Enum, auto
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
from itertools import count
|
from itertools import count
|
||||||
from time import monotonic
|
from time import monotonic, time_ns
|
||||||
from typing import IO, Any, Callable, DefaultDict, Deque, Dict, Iterable, Iterator, List, Optional, Tuple, Union, cast
|
from typing import IO, Any, Callable, DefaultDict, Deque, Dict, Iterable, Iterator, List, Optional, Tuple, Union, cast
|
||||||
|
|
||||||
from kittens.transfer.librsync import LoadSignature, PatchFile, delta_for_file, signature_of_file
|
from kittens.transfer.librsync import LoadSignature, PatchFile, delta_for_file, signature_of_file
|
||||||
from kittens.transfer.utils import IdentityCompressor, ZlibCompressor, abspath, expand_home, home_path
|
from kittens.transfer.utils import IdentityCompressor, ZlibCompressor, abspath, expand_home, home_path
|
||||||
from kitty.fast_data_types import FILE_TRANSFER_CODE, OSC, add_timer, base64_encode, get_boss, get_options
|
from kitty.fast_data_types import FILE_TRANSFER_CODE, OSC, AES256GCMDecrypt, add_timer, base64_encode, get_boss, get_options
|
||||||
from kitty.types import run_once
|
from kitty.types import run_once
|
||||||
|
|
||||||
from .utils import log_error
|
from .utils import log_error
|
||||||
@@ -252,7 +253,7 @@ def b64decode(val: memoryview) -> bytes:
|
|||||||
if extra != 0:
|
if extra != 0:
|
||||||
padding = b'=' * (4 - extra)
|
padding = b'=' * (4 - extra)
|
||||||
val = memoryview(bytes(val) + padding)
|
val = memoryview(bytes(val) + padding)
|
||||||
return standard_b64decode(val)
|
return base64.standard_b64decode(val)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -494,6 +495,29 @@ class DestFile:
|
|||||||
self.apply_metadata()
|
self.apply_metadata()
|
||||||
|
|
||||||
|
|
||||||
|
def check_bypass(password: str, request_id: str, bypass_data: str) -> bool:
|
||||||
|
protocol, sep, bypass_data = bypass_data.partition(':')
|
||||||
|
if protocol == 'kitty-1':
|
||||||
|
try:
|
||||||
|
pcmd = json.loads(bypass_data)
|
||||||
|
pubkey = pcmd.get('pubkey', '')
|
||||||
|
if not pubkey:
|
||||||
|
return False
|
||||||
|
ekey = get_boss().encryption_key
|
||||||
|
d = AES256GCMDecrypt(ekey.derive_secret(base64.b85decode(pubkey)), base64.b85decode(pcmd['iv']), base64.b85decode(pcmd['tag']))
|
||||||
|
data = d.add_data_to_be_decrypted(base64.b85decode(pcmd['encrypted']), True)
|
||||||
|
timestamp, sep, payload = data.decode('utf-8').partition(':')
|
||||||
|
delta = time_ns() - int(timestamp)
|
||||||
|
if abs(delta) > 5 * 60 * 1e9:
|
||||||
|
return False
|
||||||
|
return payload == f'{request_id};{password}'
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
elif protocol == 'sha256':
|
||||||
|
return (encode_bypass(request_id, password) == bypass_data) if password else False
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class ActiveReceive:
|
class ActiveReceive:
|
||||||
id: str
|
id: str
|
||||||
files: Dict[str, DestFile]
|
files: Dict[str, DestFile]
|
||||||
@@ -504,7 +528,7 @@ class ActiveReceive:
|
|||||||
self.bypass_ok: Optional[bool] = None
|
self.bypass_ok: Optional[bool] = None
|
||||||
if bypass:
|
if bypass:
|
||||||
byp = get_options().file_transfer_confirmation_bypass
|
byp = get_options().file_transfer_confirmation_bypass
|
||||||
self.bypass_ok = (encode_bypass(request_id, byp) == bypass) if byp else False
|
self.bypass_ok = check_bypass(byp, request_id, bypass)
|
||||||
self.files = {}
|
self.files = {}
|
||||||
self.last_activity_at = monotonic()
|
self.last_activity_at = monotonic()
|
||||||
self.send_acknowledgements = quiet < 1
|
self.send_acknowledgements = quiet < 1
|
||||||
|
|||||||
@@ -11,9 +11,12 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/jamesruan/go-rfc1924/base85"
|
|
||||||
"kitty/tools/utils"
|
"kitty/tools/utils"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/jamesruan/go-rfc1924/base85"
|
||||||
)
|
)
|
||||||
|
|
||||||
func curve25519_key_pair() (private_key []byte, public_key []byte, err error) {
|
func curve25519_key_pair() (private_key []byte, public_key []byte, err error) {
|
||||||
@@ -103,6 +106,15 @@ func EncodePublicKey(pubkey []byte, encryption_protocol string) (ans string, err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DecodePublicKey(raw string) (encryption_protocol string, pubkey []byte, err error) {
|
||||||
|
encryption_protocol, encoded_pubkey, found := strings.Cut(raw, ":")
|
||||||
|
if !found {
|
||||||
|
return "", nil, fmt.Errorf("Invalid encoded pubkey, no : in string")
|
||||||
|
}
|
||||||
|
pubkey, err = b85_decode(encoded_pubkey)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func Encrypt_cmd(cmd *utils.RemoteControlCmd, password string, other_pubkey []byte, encryption_protocol string) (encrypted_cmd utils.EncryptedRemoteControlCmd, err error) {
|
func Encrypt_cmd(cmd *utils.RemoteControlCmd, password string, other_pubkey []byte, encryption_protocol string) (encrypted_cmd utils.EncryptedRemoteControlCmd, err error) {
|
||||||
cmd.Password = password
|
cmd.Password = password
|
||||||
cmd.Timestamp = time.Now().UnixNano()
|
cmd.Timestamp = time.Now().UnixNano()
|
||||||
@@ -119,4 +131,21 @@ func Encrypt_cmd(cmd *utils.RemoteControlCmd, password string, other_pubkey []by
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Encrypt_data(data []byte, other_pubkey []byte, encryption_protocol string) (ans []byte, err error) {
|
||||||
|
d := make([]byte, 0, len(data)+32)
|
||||||
|
d = append(d, []byte(fmt.Sprintf("%s:", strconv.FormatInt(time.Now().UnixNano(), 10)))...)
|
||||||
|
d = append(d, data...)
|
||||||
|
iv, tag, ciphertext, pubkey, err := encrypt(d, other_pubkey, encryption_protocol)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ec := utils.EncryptedRemoteControlCmd{
|
||||||
|
IV: b85_encode(iv), Tag: b85_encode(tag), Pubkey: b85_encode(pubkey), Encrypted: b85_encode(ciphertext)}
|
||||||
|
if encryption_protocol != "1" {
|
||||||
|
ec.EncProto = encryption_protocol
|
||||||
|
}
|
||||||
|
ans, err = json.Marshal(ec)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// }}}
|
// }}}
|
||||||
|
|||||||
Reference in New Issue
Block a user