# -*- coding: utf-8 -*- # import base64 import hashlib import json import os import re import time from io import StringIO import paramiko import sshpubkeys from cryptography.hazmat.primitives import serialization from django.conf import settings from django.core.serializers.json import DjangoJSONEncoder from itsdangerous import ( TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer, BadSignature, SignatureExpired ) from six import string_types from .http import http_date UUID_PATTERN = re.compile(r'[0-9a-zA-Z\-]{36}') class Singleton(type): def __init__(cls, *args, **kwargs): cls.__instance = None super().__init__(*args, **kwargs) def __call__(cls, *args, **kwargs): if cls.__instance is None: cls.__instance = super().__call__(*args, **kwargs) return cls.__instance else: return cls.__instance class Signer(metaclass=Singleton): """用来加密,解密,和基于时间戳的方式验证token""" def __init__(self, secret_key=None): self.secret_key = secret_key def sign(self, value): s = JSONWebSignatureSerializer(self.secret_key, algorithm_name='HS256') return s.dumps(value).decode() def unsign(self, value): if value is None: return value s = JSONWebSignatureSerializer(self.secret_key, algorithm_name='HS256') try: return s.loads(value) except BadSignature: return None def sign_t(self, value, expires_in=3600): s = TimedJSONWebSignatureSerializer(self.secret_key, expires_in=expires_in) return str(s.dumps(value), encoding="utf8") def unsign_t(self, value): s = TimedJSONWebSignatureSerializer(self.secret_key) try: return s.loads(value) except (BadSignature, SignatureExpired): return None _supported_paramiko_ssh_key_types = ( paramiko.RSAKey, paramiko.DSSKey, paramiko.Ed25519Key, paramiko.ECDSAKey,) def ssh_key_string_to_obj(text, password=None): key = None for ssh_key_type in _supported_paramiko_ssh_key_types: try: key = ssh_key_type.from_private_key(StringIO(text), password=password) return key except paramiko.SSHException: pass if key is None: raise ValueError('Invalid private key') return key def ssh_private_key_gen(private_key, password=None): if isinstance(private_key, bytes): private_key = private_key.decode("utf-8") if isinstance(private_key, string_types): private_key = ssh_key_string_to_obj(private_key, password=password) return private_key def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost', password=None): private_key = ssh_private_key_gen(private_key, password=password) if not isinstance(private_key, _supported_paramiko_ssh_key_types): raise IOError('Invalid private key') public_key = "%(key_type)s %(key_content)s %(username)s@%(hostname)s" % { 'key_type': private_key.get_name(), 'key_content': private_key.get_base64(), 'username': username, 'hostname': hostname, } return public_key def ssh_key_gen(length=2048, type='rsa', password=None, username='jumpserver', hostname=None): """Generate user ssh private and public key Use paramiko RSAKey generate it. :return private key str and public key str """ if hostname is None: hostname = os.uname()[1] f = StringIO() try: if type == 'rsa': private_key_obj = paramiko.RSAKey.generate(length) elif type == 'dsa': private_key_obj = paramiko.DSSKey.generate(length) else: raise IOError('SSH private key must be `rsa` or `dsa`') private_key_obj.write_private_key(f, password=password) private_key = f.getvalue() public_key = ssh_pubkey_gen(private_key_obj, username=username, hostname=hostname) return private_key, public_key except IOError: raise IOError('These is error when generate ssh key.') def validate_ssh_private_key(text, password=None): key = parse_ssh_private_key_str(text, password=password) return bool(key) def parse_ssh_private_key_str(text: bytes, password=None) -> str: private_key = _parse_ssh_private_key(text, password=password) if private_key is None: return "" # 解析之后,转换成 openssh 格式的私钥 private_key_bytes = private_key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.OpenSSH, serialization.NoEncryption() ) return private_key_bytes.decode('utf-8') def parse_ssh_public_key_str(text: bytes = "", password=None) -> str: private_key = _parse_ssh_private_key(text, password=password) if private_key is None: return "" public_key_bytes = private_key.public_key().public_bytes( serialization.Encoding.OpenSSH, serialization.PublicFormat.OpenSSH, ) return public_key_bytes.decode('utf-8') def _parse_ssh_private_key(text, password=None): """ text: bytes password: str return:private key types: ec.EllipticCurvePrivateKey, rsa.RSAPrivateKey, dsa.DSAPrivateKey, ed25519.Ed25519PrivateKey, """ if not bool(password): password = None if isinstance(text, str): try: text = text.encode("utf-8") except UnicodeDecodeError: return None if isinstance(password, str): try: password = password.encode("utf-8") except UnicodeDecodeError: return None try: if is_openssh_format_key(text): return serialization.load_ssh_private_key(text, password=password) return serialization.load_pem_private_key(text, password=password) except (ValueError, TypeError): return None def is_openssh_format_key(text: bytes): return text.startswith(b"-----BEGIN OPENSSH PRIVATE KEY-----") def validate_ssh_public_key(text): ssh = sshpubkeys.SSHKey(text) try: ssh.parse() except (sshpubkeys.InvalidKeyException, UnicodeDecodeError): return False except NotImplementedError as e: return False return True def content_md5(data): """计算data的MD5值,经过Base64编码并返回str类型。 返回值可以直接作为HTTP Content-Type头部的值 """ if isinstance(data, str): data = hashlib.md5(data.encode('utf-8')) value = base64.b64encode(data.hexdigest().encode('utf-8')) return value.decode('utf-8') def make_signature(access_key_secret, date=None): if isinstance(date, bytes): date = bytes.decode(date) if isinstance(date, int): date_gmt = http_date(date) elif date is None: date_gmt = http_date(int(time.time())) else: date_gmt = date data = str(access_key_secret) + "\n" + date_gmt return content_md5(data) def encrypt_password(password, salt=None, algorithm='sha512'): from passlib.hash import sha512_crypt, des_crypt def sha512(): return sha512_crypt.using(rounds=5000).hash(password, salt=salt) def des(): return des_crypt.hash(password, salt=salt[:2]) support_algorithm = { 'sha512': sha512, 'des': des } if isinstance(algorithm, str): algorithm = algorithm.lower() if algorithm not in support_algorithm.keys(): algorithm = 'sha512' if password and support_algorithm[algorithm]: return support_algorithm[algorithm]() return None def get_signer(): s = Signer(settings.SECRET_KEY) return s signer = get_signer() def ensure_last_char_is_ascii(data): remain = '' def data_to_json(data, sort_keys=True, indent=2, cls=None): if cls is None: cls = DjangoJSONEncoder return json.dumps(data, ensure_ascii=False, sort_keys=sort_keys, indent=indent, cls=cls)