def check_password(self, user_id: str, password: str):
        public_key_hash = bytes.fromhex(user_id.split(":", 1)[0][1:])
        #signature_type = password.split(":")[0]
        signature = bytes.fromhex(password.split(":")[1])
        public_key = bytes.fromhex(password.split(":")[2])

        public_key_digest = pysodium.crypto_generichash(public_key)

        if public_key_hash.hex() == public_key_digest.hex():
            try:
                message_digest = pysodium.crypto_generichash(
                    u"login:{}".format(int(time.time()/(5*60))).encode())
                pysodium.crypto_sign_verify_detached(
                    signature, message_digest, public_key)
                if not (yield self.account_handler.check_user_exists(user_id)):
                    self.log.info(
                        "First user login, registering: user=%r", user_id.lower())
                    yield self.account_handler.register(localpart=public_key_digest.hex())
                defer.returnValue(True)
            except Exception as exception:
                self.log.info(
                    "Got exception while verifying signature: "+str(exception))
                defer.returnValue(False)
        else:
            self.log.info(
                "pubkey hash did not match pubkey")
            defer.returnValue(False)
Example #2
0
def signature_verifier_check (_peer_sign_pub_key, _local_sess_pub_key, _verifier) :
	
	log ("[e7943771]", "[crypto][signature]", "verifying `%s`...", _verifier.encode ("b64"))
	
	pysodium.crypto_sign_verify_detached (_verifier, _local_sess_pub_key, _peer_sign_pub_key)
	
	log ("[37e1f393]", "[crypto][signature]", "verified `%s`;", _local_sess_pub_key.encode ("b64"))
Example #3
0
 def test_private_key_no_encrypt(self):
     crypto.input = lambda prompt: 'n'
     private_key, public_key = crypto.make_keypair()
     key = crypto.unlock_private_key(private_key)
     sig = pysodium.crypto_sign_detached('test', key)
     pysodium.crypto_sign_verify_detached(sig, 'test',
                                          base64.b64decode(public_key))
Example #4
0
    def verify(self, data: bytes, key: PublicKey) -> bool:
        """Verifies the signature against the provided data and the public key."""

        try:
            crypto_sign_verify_detached(self.value, data, key.value)
            return True
        except ValueError:
            # ValueError is raised if verification fails.
            return False
def authenticate():
    """ This does two things, either validate a pre-existing session token
    or create a new one from a signed authentication token. """

    client_ip     = request.environ['REMOTE_ADDR']
    repository    = request.headers['repository']
    if repository not in config['repositories']: return fail(no_such_repo_msg)

    # ==
    repository_path = config['repositories'][repository]['path']
    conn = auth_db_connect(cpjoin(repository_path, 'auth_transient.db')); gc_tokens(conn)
    gc_tokens(conn)

    # Allow resume of an existing session
    if 'session_token' in request.headers:
        session_token = request.headers['session_token']

        conn.execute("delete from session_tokens where expires < ?", (time.time(),)); conn.commit()
        res = conn.execute("select * from session_tokens where token = ? and ip = ?", (session_token, client_ip)).fetchall()
        if res != []: return success({'session_token'  : session_token})
        else:         return fail(user_auth_fail_msg)

    # Create a new session
    else:
        user       = request.headers['user']
        auth_token = request.headers['auth_token']
        signiture  = request.headers['signature']

        try:
            public_key = config['users'][user]['public_key']

            # signature
            pysodium.crypto_sign_verify_detached(base64.b64decode(signiture), auth_token, base64.b64decode(public_key))

            # check token was previously issued by this system and is still valid
            res = conn.execute("select * from tokens where token = ? and ip = ? ", (auth_token, client_ip)).fetchall()

            # Validate token matches one we sent
            if res == [] or len(res) > 1: return fail(user_auth_fail_msg)

            # Does the user have permission to use this repository?
            if repository not in config['users'][user]['uses_repositories']: return fail(user_auth_fail_msg)

            # Everything OK
            conn.execute("delete from tokens where token = ?", (auth_token,)); conn.commit()

            # generate a session token and send it to the client
            session_token = base64.b64encode(pysodium.randombytes(35))
            conn.execute("insert into session_tokens (token, expires, ip, username) values (?,?,?, ?)",
                         (session_token, time.time() + extend_session_duration, client_ip, user))
            conn.commit()
            return success({'session_token'  : session_token})

        except Exception: # pylint: disable=broad-except
            return fail(user_auth_fail_msg)
Example #6
0
    def is_valid_event(self, h, ev):
        try:
            crypto_sign_verify_detached(ev.s, dumps(ev[:-1]), ev.c)
        except ValueError:
            return False

        return (
            crypto_generichash(dumps(ev)) == h and
            (ev.p == () or
             (len(ev.p) == 2 and ev.p[0] in self.hg and ev.p[1] in self.hg
              and self.hg[ev.p[0]].c == ev.c and self.hg[ev.p[1]].c != ev.c)))
Example #7
0
def verify(message: bytes, signature: bytes, public_key: bytes) -> bool:
    """Verify an unsigned message with accompanying signature.

    :param message: The unsigned message to verify
    :param signature: The signature to be verified
    :param public_key: The public key to use during verifying
    """
    try:
        crypto_sign_verify_detached(signature, message, public_key)
    except ValueError:
        return False

    return True
Example #8
0
def validate(data):
  rc = bencode.bdecode(data)
  if b'z' not in rc or b'k' not in rc:
    return False
  sig = rc[b'z']
  rc[b'z'] = b'\x00' * 64
  buf = bencode.bencode(rc)
  try:
    k = rc[b'k']
    pysodium.crypto_sign_verify_detached(sig, buf, k)
  except:
    return False
  else:
    return not _expired(rc[b't'])
Example #9
0
File: test.py Project: ctpo6/c2c
def on_message(ws, data):
    #print("msg: ", data)
    msg = json.loads(data)
    #id = "".join(map(chr, (msg['id'])))
    id = msg['id']
    if id == get_identify_reply_id:
        global remote_node_id

        pk = bytes(msg['result'][0])
        sg = bytes(msg['result'][1])

        remote_node_id = pk

        hex_id = binascii.hexlify(remote_node_id).decode("utf-8")

        try:
            pysodium.crypto_sign_verify_detached(sg, get_identify_reply_tn, pk)
            print("OK!!! node id: ", hex_id)
        except:
            print("error identify node id: ", hex_id)

        do_send_put(ws)
        return

    if id == get_reply_id:
        print("GET result!!!:\n", msg)
        return

    if id == get_fin_reply_id:
        print("GET finally result!!!:\n", msg)
        return

    if msg["method"] == "get_identify":
        tn = bytes(msg['params'][0])
        digest = binascii.unhexlify(
            b'DAFBABCBC50DF4BB432C72F610878A6990E85734')
        with_digest = msg['params'][1]
        sm = tn
        if with_digest:
            sm.append(digest)
        sg = pysodium.crypto_sign_detached(sm, secretkey)
        ws.send(
            json.dumps({
                "jsonrpc":
                "2.0",
                "id":
                msg['id'],
                "result": (list(bytearray(publickey)), list(bytearray(sg)))
            }))
Example #10
0
    def verify(self, signature: Union[str, bytes],
               message: Union[str, bytes]) -> None:
        """Verify signature, raise exception if it is not valid.

        :param message: sequance of bytes, raw format or hexadecimal notation
        :param signature: a signature in base58 encoding
        :raises: ValueError if signature is not valid
        """
        encoded_signature = scrub_input(signature)
        encoded_message = scrub_input(message)

        if not self.public_point:
            raise ValueError("Cannot verify without a public key")

        if encoded_signature[:3] != b'sig':  # not generic
            if self.curve != encoded_signature[:2]:  # "sp", "p2" "ed"
                raise ValueError("Signature and public key curves mismatch.")

        decoded_signature = base58_decode(encoded_signature)

        # Ed25519
        if self.curve == b"ed":
            digest = pysodium.crypto_generichash(encoded_message)
            try:
                pysodium.crypto_sign_verify_detached(decoded_signature, digest,
                                                     self.public_point)
            except ValueError as exc:
                raise ValueError('Signature is invalid.') from exc
        # Secp256k1
        elif self.curve == b"sp":
            pk = secp256k1.PublicKey(self.public_point, raw=True)
            sig = pk.ecdsa_deserialize_compact(decoded_signature)
            if not pk.ecdsa_verify(encoded_message, sig, digest=blake2b_32):
                raise ValueError('Signature is invalid.')
        # P256
        elif self.curve == b"p2":
            pk = fastecdsa.encoding.sec1.SEC1Encoder.decode_public_key(
                self.public_point, curve=fastecdsa.curve.P256)
            r, s = bytes_to_int(decoded_signature[:32]), bytes_to_int(
                decoded_signature[32:])
            if not fastecdsa.ecdsa.verify(
                    sig=(r, s), msg=encoded_message, Q=pk,
                    hashfunc=blake2b_32):
                raise ValueError('Signature is invalid.')
        else:
            raise Exception(
                f'Unknown elliptic curve {self.curve}')  # type: ignore
Example #11
0
def check_json_signature(message):
    message = message.copy()
    sender = message['sender']
    del message['sender']
    signature = message['signature']
    del message['signature']

    canonicalized = json.dumps(message, separators=(',', ':'), sort_keys=True)
    return pysodium.crypto_sign_verify_detached(base64.b64decode(signature), canonicalized, base64.b64decode(sender))
Example #12
0
def verifyEd25519(sig, msg, vk):
    """
    Returns True if signature sig of message msg is verified with
    verification key vk Otherwise False
    All of sig, msg, vk are bytes
    """
    try:
        result = pysodium.crypto_sign_verify_detached(sig, msg, vk)
    except Exception as ex:
        return False
    return (True if result else False)
Example #13
0
def check_json_signature(message):
    message = message.copy()
    sender = message['sender']
    del message['sender']
    signature = message['signature']
    del message['signature']

    canonicalized = json.dumps(message, separators=(',', ':'), sort_keys=True)
    return pysodium.crypto_sign_verify_detached(base64.b64decode(signature),
                                                canonicalized,
                                                base64.b64decode(sender))
Example #14
0
def auth(s, id, alpha):
    pk = load_blob(id, 'pub', 32)
    if pk is None:
        print('no pubkey found in %s' % id)
        fail(s)
    nonce = pysodium.randombytes(32)
    k = load_blob(id, 'key')
    if k is not None:
        try:
            beta = sphinxlib.respond(alpha, k)
        except:
            fail(s)
    else:
        beta = b''
    s.send(b''.join([beta, nonce]))
    sig = s.recv(64)
    try:
        pysodium.crypto_sign_verify_detached(sig, nonce, pk)
    except:
        print('bad sig')
        fail(s)
Example #15
0
    def verify(self, signature, message):
        """
        Verify signature, raise exception if it is not valid
        :param message: sequance of bytes, raw format or hexadecimal notation
        :param signature: a signature in base58 encoding
        """
        signature = scrub_input(signature)
        message = scrub_input(message)

        if not self.public_point:
            raise ValueError("Cannot verify without a public key")

        if signature[:3] != b'sig':  # not generic
            if self.curve != signature[:2]:  # "sp", "p2" "ed"
                raise ValueError("Signature and public key curves mismatch.")

        signature = base58_decode(signature)

        # Ed25519
        if self.curve == b"ed":
            digest = pysodium.crypto_generichash(message)
            try:
                pysodium.crypto_sign_verify_detached(signature, digest,
                                                     self.public_point)
            except ValueError:
                raise ValueError('Signature is invalid.')
        # Secp256k1
        elif self.curve == b"sp":
            pk = secp256k1.PublicKey(self.public_point, raw=True)
            sig = pk.ecdsa_deserialize_compact(signature)
            if not pk.ecdsa_verify(message, sig, digest=blake2b_32):
                raise ValueError('Signature is invalid.')
        # P256
        elif self.curve == b"p2":
            pk = SEC1Encoder.decode_public_key(self.public_point, curve=P256)
            r, s = bytes_to_int(signature[:32]), bytes_to_int(signature[32:])
            if not verify(sig=(r, s), msg=message, Q=pk, hashfunc=blake2b_32):
                raise ValueError('Signature is invalid.')
        else:
            assert False
Example #16
0
def verify_client(clientid, message):
    try:
        data = json.loads(message)

        idkey_base64 = data["idkey"]
        idkey = binascii.a2b_base64(idkey_base64)
        payload_str = data["payload"]
        payload_bytes = payload_str.encode("utf-8")
        sig_base64 = data["signature"]
        sig = binascii.a2b_base64(sig_base64)

        try:
            pysodium.crypto_sign_verify_detached(sig, payload_bytes, idkey)
        except ValueError:
            print("client", clientid, "bad signature")
            return False

        client_info.setdefault(clientid, set()).add(idkey_base64)
        return True
    except:
        print("client", clientid, "malformed message")
        return False
Example #17
0
def verify(token, key):
    token_header = token[:len(consts.public_header)]
    token_version = token[:2]
    if not compare_digest(token_version, consts.version):
        raise ValueError('not a v2 token')
    if not compare_digest(token_header, consts.public_header):
        raise ValueError('not a v2.public token')
    parts = token.split(b'.')
    footer = b''
    if len(parts) == 4:
        encoded_footer = parts[-1]
        footer = b64decode(encoded_footer)
    decoded = b64decode(parts[2])
    message = decoded[:-crypto_sign_BYTES]
    signature = decoded[-crypto_sign_BYTES:]
    try:
        crypto_sign_verify_detached(sig=signature,
                                    msg=pre_auth_encode(
                                        token_header, message, footer),
                                    pk=key)
    except ValueError as e:
        raise ValueError('invalid signature') from e
    return {'message': message, 'footer': footer}
 def test_private_key_no_encrypt(self):
     crypto.raw_input = lambda : 'n'
     private_key, public_key = crypto.make_keypair()
     key = crypto.unlock_private_key(private_key)
     sig = pysodium.crypto_sign_detached('test', key)
     pysodium.crypto_sign_verify_detached(sig, 'test', base64.b64decode(public_key))
Example #19
0
def parse_and_verify_pushtoken_pairing_data(plaintext):

    """
    Parses the decrypted inner layer of a pairing response
    according to the PushToken Pairing data format

    :param plaintext: The plaintext reveceived from the decryption
        of the outer pairing response layer

    :raises ParameterError: If plaintext has a wrong format

    :raises ValueError: If signature check failed

    :return: PushTokenPairingData
    """

    # ----------------------------------------------------------------------- --

    # check format boundaries

    plaintext_min_length = 1 + 4 + 32 + 1 + 1 + 1 + 64
    if len(plaintext) < plaintext_min_length:
        raise ParameterError('Malformed pairing response for type PushToken')

    # ----------------------------------------------------------------------- --

    # get user token id (unique id on the client)
    # (token type was already processed in
    #  decrypt_pairing_response function)

    #            ---------------------------------------------
    #  fields   | token type | user token id |   ...   | sign |
    #            ---------------------------------------------
    #  size     |     1      |       4       |    ?    |  64  |
    #            ---------------------------------------------

    user_token_id = struct.unpack('<I', plaintext[1:5])[0]

    # ----------------------------------------------------------------------- --

    # get user public key (next 32 bytes)

    #            ------------------------------------
    #  fields   | ... | user public key | ... | sign |
    #            ------------------------------------
    #  size     |  5  |       32        |  ?  |  64  |
    #            ------------------------------------

    user_public_key = plaintext[5:5+32]

    # ----------------------------------------------------------------------- --

    # get serial, user login and gda

    #            ----------------------------------------------------------
    #  fields   | ... | serial | NUL | user login | NUL | gda | NUL | sign |
    #            ----------------------------------------------------------
    #  size     | 37  |   ?    |  1  |     ?      |  1  |  ?  |  1  |  64  |
    #            ----------------------------------------------------------

    # parse token_serial and user identification

    str_parts = plaintext[5+32:-64].split(b'\x00')

    # enforce format

    if not len(str_parts) == 3 + 1:
        raise ParameterError('Malformed pairing response for type PushToken')

    serial = str_parts[0].decode('utf8')
    user_login = str_parts[1].decode('utf8')
    gda = str_parts[2].decode('utf8')

    # ----------------------------------------------------------------------- --

    # get signature and verify

    signature = plaintext[-64:]
    message = plaintext[:-64]

    try:
        crypto_sign_verify_detached(signature, message, user_public_key)
    except ValueError:
        # original value error is too generic
        raise ValueError('Invalid signature for pairing response data')

    # ----------------------------------------------------------------------- --

    return PushTokenPairingData(user_public_key,
                                user_token_id,
                                serial,
                                user_login,
                                gda)
Example #20
0
def parse_and_verify_pushtoken_pairing_data(plaintext):

    """
    Parses the decrypted inner layer of a pairing response
    according to the PushToken Pairing data format

    :param plaintext: The plaintext reveceived from the decryption
        of the outer pairing response layer

    :raises ParameterError: If plaintext has a wrong format

    :raises ValueError: If signature check failed

    :return: PushTokenPairingData
    """

    # --------------------------------------------------------------------------

    # check format boundaries

    plaintext_min_length = 1 + 4 + 32 + 1 + 1 + 1 + 64
    if len(plaintext) < plaintext_min_length:
        raise ParameterError('Malformed pairing response for type PushToken')

    # --------------------------------------------------------------------------

    # get user token id (unique id on the client)
    # (token type was already processed in
    #  decrypt_pairing_response function)

    #            ---------------------------------------------
    #  fields   | token type | user token id |   ...   | sign |
    #            ---------------------------------------------
    #  size     |     1      |       4       |    ?    |  64  |
    #            ---------------------------------------------

    user_token_id = struct.unpack('<I', plaintext[1:5])[0]

    # --------------------------------------------------------------------------

    # get user public key (next 32 bytes)

    #            ------------------------------------
    #  fields   | ... | user public key | ... | sign |
    #            ------------------------------------
    #  size     |  5  |       32        |  ?  |  64  |
    #            ------------------------------------

    user_public_key = plaintext[5:5+32]

    # --------------------------------------------------------------------------

    # get serial, user login and gda

    #            ----------------------------------------------------------
    #  fields   | ... | serial | NUL | user login | NUL | gda | NUL | sign |
    #            ----------------------------------------------------------
    #  size     | 37  |   ?    |  1  |     ?      |  1  |  ?  |  1  |  64  |
    #            ----------------------------------------------------------

    # parse token_serial and user identification

    str_parts = plaintext[5+32:-64].split(b'\x00')

    # enforce format

    if not len(str_parts) == 3 + 1:
        raise ParameterError('Malformed pairing response for type PushToken')

    serial = str_parts[0].decode('utf8')
    user_login = str_parts[1].decode('utf8')
    gda = str_parts[2].decode('utf8')

    # --------------------------------------------------------------------------

    # get signature and verify

    signature = plaintext[-64:]
    message = plaintext[:-64]

    try:
        crypto_sign_verify_detached(signature, message, user_public_key)
    except ValueError:
        # original value error is too generic
        raise ValueError('Invalid signature for pairing response data')

    # --------------------------------------------------------------------------

    return PushTokenPairingData(user_public_key,
                                user_token_id,
                                serial,
                                user_login,
                                gda)
Example #21
0
def verify_blob(msg, pk):
    sig = msg[-64:]
    msg = msg[:-64]
    pysodium.crypto_sign_verify_detached(sig, msg, pk)
    return msg
def authenticate(request: Request) -> Responce:
    """ This does two things, either validate a pre-existing session token
    or create a new one from a signed authentication token. """

    client_ip = request.remote_addr
    repository = request.headers['repository']
    if repository not in config['repositories']: return fail(no_such_repo_msg)

    # ==
    repository_path = config['repositories'][repository]['path']
    conn = auth_db_connect(cpjoin(repository_path, 'auth_transient.db'))
    gc_tokens(conn)
    gc_tokens(conn)

    # Allow resume of an existing session
    if 'session_token' in request.headers:
        session_token = request.headers['session_token']

        conn.execute("delete from session_tokens where expires < ?",
                     (time.time(), ))
        conn.commit()
        res = conn.execute(
            "select * from session_tokens where token = ? and ip = ?",
            (session_token, client_ip)).fetchall()
        if res != []: return success({'session_token': session_token})
        else: return fail(user_auth_fail_msg)

    # Create a new session
    else:
        user = request.headers['user']
        auth_token = request.headers['auth_token']
        signiture = request.headers['signature']

        try:
            public_key = config['users'][user]['public_key']

            # signature
            pysodium.crypto_sign_verify_detached(base64.b64decode(signiture),
                                                 auth_token,
                                                 base64.b64decode(public_key))

            # check token was previously issued by this system and is still valid
            res = conn.execute(
                "select * from tokens where token = ? and ip = ? ",
                (auth_token, client_ip)).fetchall()

            # Validate token matches one we sent
            if res == [] or len(res) > 1: return fail(user_auth_fail_msg)

            # Does the user have permission to use this repository?
            if repository not in config['users'][user]['uses_repositories']:
                return fail(user_auth_fail_msg)

            # Everything OK
            conn.execute("delete from tokens where token = ?", (auth_token, ))
            conn.commit()

            # generate a session token and send it to the client
            session_token = base64.b64encode(pysodium.randombytes(35))
            conn.execute(
                "insert into session_tokens (token, expires, ip, username) values (?,?,?, ?)",
                (session_token, time.time() + extend_session_duration,
                 client_ip, user))
            conn.commit()

            return success({'session_token': session_token})

        except Exception:  # pylint: disable=broad-except
            return fail(user_auth_fail_msg)
Example #23
0
def run_server():
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind((server_ip, server_port))

    log('[INFO] Listening on '+server_ip+':'+str(server_port))

    # NONCE(24) + MAC(16) + MAGIC(8) + SIGNPUB(32) + SIG(64) + COUNTER(14) + LEN(2) + REST

    PROTO_MIN_SIZE = 160

    magic_bin = '42f9708e2f1369d9'.decode('hex') # chosen by fair die
    server_ip_bin = socket.inet_aton(server_ip)

    # FIXME: lock state file for duration of the program
    counter = read_counter()

    while True:
        try:
            client_ip, client_port = (None, None)
            cdata, (client_ip, client_port) = sock.recvfrom(1024)

            if len(cdata) < PROTO_MIN_SIZE:
                # normal: a UDP scan would cause this
                log('[INFO] small packet', client_ip, client_port)
                continue

            pkt_nonce = cdata[:pysodium.crypto_secretbox_NONCEBYTES]

            try:
                plain = pysodium.crypto_secretbox_open(cdata[pysodium.crypto_secretbox_NONCEBYTES:],
                                                       pkt_nonce,
                                                       server_private_key)
            except:
                # normal-ish: any big enough packet to the port will cause
                # this, but unless knockd happens to run on a common UDP
                # port there's not much reason to see such a big packet
                log('[INFO] incorrect ciphertext', client_ip, client_port)
                continue

            pkt_magic = plain[:8]
            plain = plain[8:]

            if pkt_magic != magic_bin:
                # unrecognised protocol version
                log('[WARN] unrecognised version magic', client_ip, client_port, pkt_magic.encode('hex'))
                continue

            pkt_sign_pub = plain[:32]
            plain = plain[32:]

            if pkt_sign_pub not in client_sign_keys:
                log('[WARN] unrecognised client sign key', client_ip, client_port, pkt_sign_pub.encode('hex'))
                continue

            pkt_sig = plain[:64]
            plain = plain[64:]

            pkt_counter_bin = plain[:14]
            pkt_counter = int(pkt_counter_bin.encode('hex'), 16)
            plain = plain[14:]

            # check counter early so we don't need to verify replays
            if pkt_counter <= counter:
                log('[POSSIBLE_REPLAY] bad counter', client_ip, client_port)
                continue

            try:
                pysodium.crypto_sign_verify_detached(pkt_sig,
                                                     (pkt_nonce + magic_bin + pkt_sign_pub +
                                                      pkt_counter_bin + plain +
                                                      server_private_key + server_ip_bin),
                                                     pkt_sign_pub)
            except:
                # shenanigans, the data isn't signed by that user
                log('[SEVERE] bad signature', client_ip, client_port)
                continue

            pkt_data_len_bin = plain[:2]
            pkt_data_len = int(pkt_data_len_bin.encode('hex'), 16)
            plain = plain[2:]

            pkt_data = plain[:pkt_data_len]

            # Cool, write that counter ASAP so the packet can't be used again
            counter = pkt_counter
            write_counter(counter)

            log('[INFO] valid knock', client_ip, client_port)

            # NB. do *not* trust that client_ip is the originator (beyond
            # port knocking functionality). A person who can perform
            # packet captures can race the packet from their own IP
            subprocess.Popen([knocked_command, client_ip, pkt_data])


        except KeyboardInterrupt:
            break

        except Exception as e:
            log('[ERROR] packet exception', client_ip, client_port, e)
Example #24
0
    def verifyPropose(self, data, addr):
        propose_tuple = sts_utility.deconstructPropose(data)
        # 1. validate certificate
        # 2. validate cert status
        # 3. Message signature check
        # 4. Check capability

        # 1. Certificate Checks
        if propose_tuple['C']['I'] not in STS.CAcerts.keys():
            print('wrong issuer principal')
            return False
        for I, _ in STS.CAcerts.items():
            if propose_tuple['C']['I'] == I:
                ca_cert_tuple = STS.CAcerts[I]
        try:
            pysodium.crypto_sign_verify_detached(
                propose_tuple['C']['G'],
                propose_tuple['C_B'][:-64] + (0).to_bytes(64, byteorder='big'),
                ca_cert_tuple['K_S'])
        except:
            print('wrong certificate signature')
            return False

        # CA DEPTH
        if ca_cert_tuple['C'][0] == 0:
            print('wrong CA depth')
            return False

        if not self.subjectPrincipalCheck(addr, propose_tuple['C']):
            print('wrong subject principal')
            return False

        # 4. NODE CAPABILITIES
        if not (propose_tuple['C']['C'][1:4] == b'\x00\x00\x1f'
                or propose_tuple['C']['C'][1:4] == b'\x00\x00?'):
            print('wrong capabilities')
            return False

        # 2. Certificate Status check
        try:
            pysodium.crypto_sign_verify_detached(
                propose_tuple['S']['G'],
                propose_tuple['S_B'][:-64] + (0).to_bytes(64, byteorder='big'),
                ca_cert_tuple['K_S'])
        except:
            print('wrong status signature')
            return False

        if pysodium.crypto_generichash(propose_tuple['C_B'],
                                       outlen=64) != propose_tuple['S']['H']:
            print('wrong status HASH')
            return False

        if propose_tuple['S']['S'] != 1:
            print('wrong cert status validity')
            return False

        currTime = int(time.time())
        if propose_tuple['S']['F'] + propose_tuple['S']['U'] <= currTime:
            print('wrong cert status, validity expired')
            return False

        #  3. Message signature:
        try:
            pysodium.crypto_sign_verify_detached(
                propose_tuple['G'],
                data[:-64] + (0).to_bytes(64, byteorder='big'),
                propose_tuple['C']['K_S'])
        except:
            print('wrong propose message signature')
            return False

        return True
Example #25
0
def test_pysodium():
    """
    Test all the functions needed from pysodium libarary (libsodium)

    """
    # crypto_sign signatures with Ed25519 keys

    # create keypair without seed
    verkey, sigkey = pysodium.crypto_sign_keypair()
    assert len(verkey) == 32 == pysodium.crypto_sign_PUBLICKEYBYTES
    assert len(sigkey) == 64 == pysodium.crypto_sign_SECRETKEYBYTES

    assert 32 == pysodium.crypto_sign_SEEDBYTES
    sigseed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES)
    assert len(sigseed) == 32
    # seed = (b'J\xeb\x06\xf2BA\xd6/T\xe1\xe2\xe2\x838\x8a\x99L\xd9\xb5(\\I\xccRb\xc8\xd5\xc7Y\x1b\xb6\xf0')

    # Ann's seed
    sigseed = (
        b'PTi\x15\xd5\xd3`\xf1u\x15}^r\x9bfH\x02l\xc6\x1b\x1d\x1c\x0b9\xd7{\xc0_\xf2K\x93`'
    )
    assert len(sigseed) == 32

    #  try key stretching from 16 bytes using  pysodium.crypto_pwhash()
    assert 16 == pysodium.crypto_pwhash_SALTBYTES
    salt = pysodium.randombytes(pysodium.crypto_pwhash_SALTBYTES)
    assert len(salt) == 16
    #  salt = b'\x19?\xfa\xc7\x8f\x8b\x7f\x8b\xdbS"$\xd7[\x85\x87'

    # algorithm default is argon2id
    sigseed = pysodium.crypto_pwhash(
        outlen=32,
        passwd="",
        salt=salt,
        opslimit=pysodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,
        memlimit=pysodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,
        alg=pysodium.crypto_pwhash_ALG_DEFAULT)

    assert len(sigseed) == 32
    #  seed = (b'\xa9p\x89\x7f+\x0e\xc4\x9c\xf2\x01r\xafTI\xc0\xfa\xac\xd5\x99\xf8O\x8f=\x843\xa2\xb6e\x9fO\xff\xd0')

    # creates signing/verification key pair from seed
    verkey, sigkey = pysodium.crypto_sign_seed_keypair(sigseed)
    assert len(verkey) == 32
    assert len(sigkey) == 64
    #  sigkey is seed and verkey concatenated. Libsodium does this as an optimization
    #  because the signing scheme needs both the private key (seed) and the public key so
    #  instead of recomputing the public key each time from the secret key it requires
    #  the public key as an input of and instead of two separate inputs, one for the
    #  secret key and one for the public key, it uses a concatenated form.
    #  Essentially crypto_sign_seed_keypair and crypto_sign_keypair return redundant
    #  information in the duple (verkey, sigkey) because sigkey includes verkey
    #  so one could just store sigkey and extract verkey or sigseed when needed
    #  or one could just store verkey and sigseed and reconstruct sigkey when needed.
    #  crypto_sign_detached requires sigkey (sigseed + verkey)
    #  crypto_sign_verify_detached reqires verkey only
    #  https://crypto.stackexchange.com/questions/54353/why-are-nacl-secret-keys-64-bytes-for-signing-but-32-bytes-for-box
    assert sigseed == sigkey[:32]
    assert verkey == sigkey[32:]
    assert sigkey == sigseed + verkey
    # vk = (b'B\xdd\xbb}8V\xa0\xd6lk\xcf\x15\xad9\x1e\xa7\xa1\xfe\xe0p<\xb6\xbex\xb0s\x8d\xd6\xf5\xa5\xe8Q')

    #  utility function to extract seed from secret sigkey (really just extracting from front half)
    assert sigseed == pysodium.crypto_sign_sk_to_seed(sigkey)

    assert 64 == pysodium.crypto_sign_BYTES

    msg = "The lazy dog jumped over the river"
    msgb = msg.encode(
        "utf-8")  # must convert unicode string to bytes in order to sign it
    assert msgb == b'The lazy dog jumped over the river'
    sig = pysodium.crypto_sign_detached(msgb, sigseed +
                                        verkey)  #  sigkey = seed + verkey
    assert len(sig) == 64
    """
    sig = (b"\x99\xd2<9$$0\x9fk\xfb\x18\xa0\x8c@r\x122.k\xb2\xc7\x1fp\x0e'm\x8f@"
           b'\xaa\xa5\x8c\xc8n\x85\xc8!\xf6q\x91p\xa9\xec\xcf\x92\xaf)\xde\xca'
           b'\xfc\x7f~\xd7o|\x17\x82\x1d\xd4<o"\x81&\t')

    """
    #siga = pysodium.crypto_sign(msg.encode("utf-8"), sk)[:pysodium.crypto_sign_BYTES]
    #assert len(siga) == 64
    #assert sig == siga

    try:  #  verify returns None if valid else raises ValueError
        result = pysodium.crypto_sign_verify_detached(sig, msgb, verkey)
    except Exception as ex:
        assert False
    assert not result
    assert result is None

    sigbad = sig[:-1]
    sigbad += b'A'

    try:  #  verify returns None if valid else raises ValueError
        result = pysodium.crypto_sign_verify_detached(sigbad, msgb, verkey)
    except Exception as ex:
        assert True
        assert isinstance(ex, ValueError)

    # crypto_box authentication encryption with X25519 keys

    apubkey, aprikey = pysodium.crypto_box_keypair()
    assert len(apubkey) == 32 == pysodium.crypto_box_SECRETKEYBYTES
    assert len(aprikey) == 32 == pysodium.crypto_box_PUBLICKEYBYTES

    repubkey = pysodium.crypto_scalarmult_curve25519_base(aprikey)
    assert repubkey == apubkey

    assert 32 == pysodium.crypto_box_SEEDBYTES

    boxseed = pysodium.randombytes(pysodium.crypto_box_SEEDBYTES)
    assert len(boxseed) == 32

    bpubkey, bprikey = pysodium.crypto_box_seed_keypair(boxseed)
    assert len(bpubkey) == 32
    assert len(bprikey) == 32

    repubkey = pysodium.crypto_scalarmult_curve25519_base(bprikey)
    assert repubkey == bpubkey

    assert 24 == pysodium.crypto_box_NONCEBYTES
    nonce = pysodium.randombytes(pysodium.crypto_box_NONCEBYTES)
    assert len(nonce) == 24
    # nonce = b'\x11\xfbi<\xf2\xb6k\xa05\x0c\xf9\x86t\x07\x8e\xab\x8a\x97nG\xe8\x87,\x94'

    atob_tx = "Hi Bob I'm Alice"
    atob_txb = atob_tx.encode("utf-8")

    # Detached recomputes shared key every time.
    # A encrypt to B
    acrypt, amac = pysodium.crypto_box_detached(atob_txb, nonce, bpubkey,
                                                aprikey)
    amacl = pysodium.crypto_box_MACBYTES
    assert amacl == 16
    #  amac =  b'\xa1]\xc6ML\xe2\xa9:\xc0\xdc\xab\xa5\xc4\xc7\xf4\xdb'
    #  acrypt = (b'D\n\x17\xb6z\xd8+t)\xcc`y\x1d\x10\x0cTC\x02\xb5@\xe2\xf2\xc9-(\xec*O\xb8~\xe2\x1a\xebO')
    # when transmitting prepend amac to crypt

    acipher = pysodium.crypto_box(atob_txb, nonce, bpubkey, aprikey)
    assert acipher == amac + acrypt

    atob_rxb = pysodium.crypto_box_open_detached(acrypt, amac, nonce, apubkey,
                                                 bprikey)
    atob_rx = atob_rxb.decode("utf-8")
    assert atob_rx == atob_tx
    assert atob_rxb == atob_txb

    atob_rxb = pysodium.crypto_box_open(acipher, nonce, apubkey, bprikey)
    atob_rx = atob_rxb.decode("utf-8")
    assert atob_rx == atob_tx
    assert atob_rxb == atob_txb

    btoa_tx = "Hello Alice I am Bob"
    btoa_txb = btoa_tx.encode("utf-8")

    # B encrypt to A
    bcrypt, bmac = pysodium.crypto_box_detached(btoa_txb, nonce, apubkey,
                                                bprikey)
    # bmac = b'\x90\xe07=\xd22\x8fh2\xff\xdd\x84tC\x053'
    # bcrypt = (b'8\xb5\xba\xe7\xcc\xae B\xefx\xe6{U\xf7\xefA\x00\xc7|\xdbu\xcfc\x01$\xa9\xa2P\xa7\x84\xa5\xae\x180')
    # when transmitting prepend amac to crypt

    bcipher = pysodium.crypto_box(btoa_txb, nonce, apubkey, bprikey)
    assert bcipher == bmac + bcrypt

    btoa_rxb = pysodium.crypto_box_open_detached(bcrypt, bmac, nonce, bpubkey,
                                                 aprikey)
    btoa_rx = btoa_rxb.decode("utf-8")
    assert btoa_rx == btoa_tx
    assert btoa_rxb == btoa_txb

    btoa_rxb = pysodium.crypto_box_open(bcipher, nonce, bpubkey, aprikey)
    btoa_rx = btoa_rxb.decode("utf-8")
    assert btoa_rx == btoa_tx
    assert btoa_rxb == btoa_txb

    # compute shared key
    asymkey = pysodium.crypto_box_beforenm(bpubkey, aprikey)
    bsymkey = pysodium.crypto_box_beforenm(apubkey, bprikey)
    assert asymkey == bsymkey

    acipher = pysodium.crypto_box_afternm(atob_txb, nonce, asymkey)
    atob_rxb = pysodium.crypto_box_open_afternm(acipher, nonce, bsymkey)
    assert atob_rxb == atob_txb

    bcipher = pysodium.crypto_box_afternm(btoa_txb, nonce, bsymkey)
    btoa_rxb = pysodium.crypto_box_open_afternm(bcipher, nonce, asymkey)
    assert btoa_rxb == btoa_txb

    # crypto_box_seal public key encryption with X25519 keys
    #  uses same X25519 type of keys as crypto_box authenticated encryption
    #  so when converting sign key Ed25519 to X25519 can use for both types of encryption

    pubkey, prikey = pysodium.crypto_box_keypair()
    assert len(pubkey) == 32 == pysodium.crypto_box_PUBLICKEYBYTES
    assert len(prikey) == 32 == pysodium.crypto_box_SECRETKEYBYTES

    assert 48 == pysodium.crypto_box_SEALBYTES

    msg_txb = "Catch me if you can.".encode("utf-8")
    assert msg_txb == b'Catch me if you can.'
    cipher = pysodium.crypto_box_seal(msg_txb, pubkey)
    assert len(cipher) == 48 + len(msg_txb)

    msg_rxb = pysodium.crypto_box_seal_open(cipher, pubkey, prikey)
    assert msg_rxb == msg_txb

    #  convert Ed25519 key pair to X25519 key pair
    #  https://blog.filippo.io/using-ed25519-keys-for-encryption/
    #  https://libsodium.gitbook.io/doc/advanced/ed25519-curve25519
    #  crypto_sign_ed25519_pk_to_curve25519
    #  crypto_sign_ed25519_sk_to_curve25519

    pubkey = pysodium.crypto_sign_pk_to_box_pk(verkey)
    assert len(pubkey) == pysodium.crypto_box_PUBLICKEYBYTES

    prikey = pysodium.crypto_sign_sk_to_box_sk(sigkey)
    assert len(prikey) == pysodium.crypto_box_SECRETKEYBYTES

    repubkey = pysodium.crypto_scalarmult_curve25519_base(prikey)
    assert repubkey == pubkey

    msg_txb = "Encoded using X25519 key converted from Ed25519 key".encode(
        "utf-8")
    cipher = pysodium.crypto_box_seal(msg_txb, pubkey)
    assert len(cipher) == 48 + len(msg_txb)

    msg_rxb = pysodium.crypto_box_seal_open(cipher, pubkey, prikey)
    assert msg_rxb == msg_txb
    """
Example #26
0
    def decrypt_and_verify_challenge(self, challenge_url, action):
        """
        Decrypts the data packed in the challenge url, verifies
        its content, returns the parsed data as a dictionary,
        calculates and returns the signature.

        The calling method must then send the signature
        back to the server. (The reason for this control flow
        is that the challenge data must be checked in different
        scenarios, e.g. when we have a pairing the data must be
        checked by the method that simulates the pairing)

        :param challenge_url: the challenge url as sent by the server
        :param action: a string identifier for the verification action
            (at the moment 'ACCEPT' or 'DENY')

        :returns: (challenge, signature)

            challenge has the keys

                * content_type - one of the three values CONTENT_TYPE_SIGNREQ,
                    CONTENT_TYPE_PAIRING or CONTENT_TYPE_LOGIN)
                    (all defined in this module)
                * transaction_id - used to identify the challenge
                    on the server
                * callback_url (optional) - the url to which the challenge
                    response should be set
                * user_token_id - used to identify the token in the
                    user database for which this challenge was created

            depending on the content type additional keys are present

                * for CONTENT_TYPE_PAIRING: serial
                * for CONTENT_TYPE_SIGNREQ: message
                * for CONTENT_TYPE_LOGIN: login, host

            signature is the generated user signature used to
            respond to the challenge
        """

        challenge_data_encoded = challenge_url[len(self.uri + '://chal/'):]
        challenge_data = decode_base64_urlsafe(challenge_data_encoded)

        # ------------------------------------------------------------------ --

        # parse and verify header information in the
        # encrypted challenge data

        header = challenge_data[0:5]
        version, user_token_id = struct.unpack('<bI', header)
        self.assertEqual(version, CHALLENGE_URL_VERSION)

        # ------------------------------------------------------------------ --

        # get token from client token database

        token = self.tokens[user_token_id]
        server_public_key = token['server_public_key']

        # ------------------------------------------------------------------ --

        # prepare decryption by seperating R from
        # ciphertext and server signature

        R = challenge_data[5:5 + 32]
        ciphertext = challenge_data[5 + 32:-64]
        server_signature = challenge_data[-64:]

        # check signature

        data = challenge_data[0:-64]
        crypto_sign_verify_detached(server_signature, data, server_public_key)

        # ------------------------------------------------------------------ --

        # key derivation

        secret_key_dh = dsa_to_dh_secret(self.secret_key)
        ss = calc_dh(secret_key_dh, R)
        U = SHA256.new(ss).digest()

        sk = U[0:16]
        nonce = U[16:32]

        # ------------------------------------------------------------------ --

        # decrypt and verify challenge

        nonce_as_int = int_from_bytes(nonce, byteorder='big')
        ctr = Counter.new(128, initial_value=nonce_as_int)
        cipher = AES.new(sk, AES.MODE_CTR, counter=ctr)
        plaintext = cipher.decrypt(ciphertext)

        # ------------------------------------------------------------------ --

        # parse/check plaintext header

        # 1 - for content type
        # 8 - for transaction id
        # 8 - for time stamp
        offset = 1 + 8 + 8

        pt_header = plaintext[0:offset]
        (content_type, transaction_id,
         _time_stamp) = struct.unpack('<bQQ', pt_header)

        transaction_id = u64_to_transaction_id(transaction_id)

        # ------------------------------------------------------------------ --

        # prepare the parsed challenge data

        challenge = {}
        challenge['content_type'] = content_type

        # ------------------------------------------------------------------ --

        # retrieve plaintext data depending on content_type

        if content_type == CONTENT_TYPE_PAIRING:

            serial, callback_url, __ = plaintext[offset:].split('\x00')
            challenge['serial'] = serial

        elif content_type == CONTENT_TYPE_SIGNREQ:

            message, callback_url, __ = plaintext[offset:].split('\x00')
            challenge['message'] = message

        elif content_type == CONTENT_TYPE_LOGIN:

            login, host, callback_url, __ = plaintext[offset:].split('\x00')
            challenge['login'] = login
            challenge['host'] = host

        # ------------------------------------------------------------------ --

        # prepare the parsed challenge data

        challenge['callback_url'] = callback_url
        challenge['transaction_id'] = transaction_id
        challenge['user_token_id'] = user_token_id

        # calculate signature

        sig_base = (struct.pack('<b', CHALLENGE_URL_VERSION) +
                    b'%s\0' % action + server_signature + plaintext)

        sig = crypto_sign_detached(sig_base, self.secret_key)
        encoded_sig = encode_base64_urlsafe(sig)

        return challenge, encoded_sig
Example #27
0
    def decrypt_and_verify_challenge(self, challenge_url, action):

        """
        Decrypts the data packed in the challenge url, verifies
        its content, returns the parsed data as a dictionary,
        calculates and returns the signature.

        The calling method must then send the signature
        back to the server. (The reason for this control flow
        is that the challenge data must be checked in different
        scenarios, e.g. when we have a pairing the data must be
        checked by the method that simulates the pairing)

        :param challenge_url: the challenge url as sent by the server
        :param action: a string identifier for the verification action
            (at the moment 'ACCEPT' or 'DENY')

        :returns: (challenge, signature)

            challenge has the keys

                * content_type - one of the three values CONTENT_TYPE_SIGNREQ,
                    CONTENT_TYPE_PAIRING or CONTENT_TYPE_LOGIN)
                    (all defined in this module)
                * transaction_id - used to identify the challenge
                    on the server
                * callback_url (optional) - the url to which the challenge
                    response should be set
                * user_token_id - used to identify the token in the
                    user database for which this challenge was created

            depending on the content type additional keys are present

                * for CONTENT_TYPE_PAIRING: serial
                * for CONTENT_TYPE_SIGNREQ: message
                * for CONTENT_TYPE_LOGIN: login, host

            signature is the generated user signature used to
            respond to the challenge
        """

        challenge_data_encoded = challenge_url[len(self.uri + '://chal/'):]
        challenge_data = decode_base64_urlsafe(challenge_data_encoded)

        # ------------------------------------------------------------------ --

        # parse and verify header information in the
        # encrypted challenge data

        header = challenge_data[0:5]
        version, user_token_id = struct.unpack('<bI', header)
        self.assertEqual(version, CHALLENGE_URL_VERSION)

        # ------------------------------------------------------------------ --

        # get token from client token database

        token = self.tokens[user_token_id]
        server_public_key = token['server_public_key']

        # ------------------------------------------------------------------ --

        # prepare decryption by seperating R from
        # ciphertext and server signature

        R = challenge_data[5:5 + 32]
        ciphertext = challenge_data[5 + 32:-64]
        server_signature = challenge_data[-64:]

        # check signature

        data = challenge_data[0:-64]
        crypto_sign_verify_detached(server_signature, data, server_public_key)

        # ------------------------------------------------------------------ --

        # key derivation

        secret_key_dh = dsa_to_dh_secret(self.secret_key)
        ss = calc_dh(secret_key_dh, R)
        U = SHA256.new(ss).digest()

        sk = U[0:16]
        nonce = U[16:32]

        # ------------------------------------------------------------------ --

        # decrypt and verify challenge

        nonce_as_int = int_from_bytes(nonce, byteorder='big')
        ctr = Counter.new(128, initial_value=nonce_as_int)
        cipher = AES.new(sk, AES.MODE_CTR, counter=ctr)
        plaintext = cipher.decrypt(ciphertext)

        # ------------------------------------------------------------------ --

        # parse/check plaintext header

        # 1 - for content type
        # 8 - for transaction id
        # 8 - for time stamp
        offset = 1 + 8 + 8

        pt_header = plaintext[0:offset]
        (content_type,
         transaction_id,
         _time_stamp) = struct.unpack('<bQQ', pt_header)

        transaction_id = u64_to_transaction_id(transaction_id)

        # ------------------------------------------------------------------ --

        # prepare the parsed challenge data

        challenge = {}
        challenge['content_type'] = content_type

        # ------------------------------------------------------------------ --

        # retrieve plaintext data depending on content_type

        if content_type == CONTENT_TYPE_PAIRING:

            serial, callback_url, __ = plaintext[offset:].split('\x00')
            challenge['serial'] = serial

        elif content_type == CONTENT_TYPE_SIGNREQ:

            message, callback_url, __ = plaintext[offset:].split('\x00')
            challenge['message'] = message

        elif content_type == CONTENT_TYPE_LOGIN:

            login, host, callback_url, __ = plaintext[offset:].split('\x00')
            challenge['login'] = login
            challenge['host'] = host

        # ------------------------------------------------------------------ --

        # prepare the parsed challenge data

        challenge['callback_url'] = callback_url
        challenge['transaction_id'] = transaction_id
        challenge['user_token_id'] = user_token_id

        # calculate signature

        sig_base = (
            struct.pack('<b', CHALLENGE_URL_VERSION) +
            b'%s\0' % action +
            server_signature + plaintext)

        sig = crypto_sign_detached(sig_base, self.secret_key)
        encoded_sig = encode_base64_urlsafe(sig)

        return challenge, encoded_sig