File: mac.py

package info (click to toggle)
python-asyncssh 2.21.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,464 kB
  • sloc: python: 40,306; makefile: 11
file content (212 lines) | stat: -rw-r--r-- 7,140 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# Copyright (c) 2013-2021 by Ron Frederick <ronf@timeheart.net> and others.
#
# This program and the accompanying materials are made available under
# the terms of the Eclipse Public License v2.0 which accompanies this
# distribution and is available at:
#
#     http://www.eclipse.org/legal/epl-2.0/
#
# This program may also be made available under the following secondary
# licenses when the conditions for such availability set forth in the
# Eclipse Public License v2.0 are satisfied:
#
#    GNU General Public License, Version 2.0, or any later versions of
#    that license
#
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
#
# Contributors:
#     Ron Frederick - initial implementation, API, and documentation

"""SSH message authentication handlers"""

from hashlib import md5, sha1, sha224, sha256, sha384, sha512
import hmac
from typing import Dict, Callable, List, Tuple

from .packet import UInt32, UInt64

try:
    from .crypto import umac64, umac128
    _umac_available = True
except ImportError: # pragma: no cover
    _umac_available = False


_MACAlgsArgs = Tuple[bytes, int, int, bool, Callable, Tuple, bool]
_MACHandler = Tuple[Callable, int, Tuple]
_MACParams = Tuple[int, int, bool]


_OPENSSH = b'@openssh.com'
_ETM = b'-etm' + _OPENSSH

_mac_algs: List[bytes] = []
_default_mac_algs: List[bytes] = []
_mac_handler: Dict[bytes, _MACHandler] = {}
_mac_params: Dict[bytes, _MACParams] = {}


class MAC:
    """Parent class for SSH message authentication handlers"""

    def __init__(self, key: bytes, hash_size: int):
        self._key = key
        self._hash_size = hash_size

    def sign(self, seq: int, packet: bytes) -> bytes:
        """Compute a signature for a message"""

        raise NotImplementedError

    def verify(self, seq: int, packet: bytes, sig: bytes) -> bool:
        """Verify the signature of a message"""

        raise NotImplementedError


class _NullMAC(MAC):
    """Null message authentication handler"""

    def sign(self, seq: int, packet: bytes) -> bytes:
        """Compute a signature for a message"""

        return b''

    def verify(self, seq: int, packet: bytes, sig: bytes) -> bool:
        """Verify the signature of a message"""

        return sig == b''


class _HMAC(MAC):
    """HMAC-based message authentication handler"""

    def __init__(self, key: bytes, hash_size: int, hash_alg: Callable):
        super().__init__(key, hash_size)
        self._hash_alg = hash_alg

    def sign(self, seq: int, packet: bytes) -> bytes:
        """Compute a signature for a message"""

        data = UInt32(seq) + packet
        sig = hmac.new(self._key, data, self._hash_alg).digest()
        return sig[:self._hash_size]

    def verify(self, seq: int, packet: bytes, sig: bytes) -> bool:
        """Verify the signature of a message"""

        return hmac.compare_digest(self.sign(seq, packet), sig)


class _UMAC(MAC):
    """UMAC-based message authentication handler"""

    def __init__(self, key: bytes, hash_size: int, umac_alg: Callable):
        super().__init__(key, hash_size)
        self._umac_alg = umac_alg

    def sign(self, seq: int, packet: bytes) -> bytes:
        """Compute a signature for a message"""

        return self._umac_alg(self._key, packet, UInt64(seq)).digest()

    def verify(self, seq: int, packet: bytes, sig: bytes) -> bool:
        """Verify the signature of a message"""

        return hmac.compare_digest(self.sign(seq, packet), sig)


def register_mac_alg(mac_alg: bytes, key_size: int, hash_size: int,
                     etm: bool, handler: Callable, args: Tuple,
                     default: bool) -> None:
    """Register a MAC algorithm"""

    if mac_alg:
        _mac_algs.append(mac_alg)

        if default:
            _default_mac_algs.append(mac_alg)

    _mac_handler[mac_alg] = (handler, hash_size, args)
    _mac_params[mac_alg] = (key_size, hash_size, etm)


def get_mac_algs() -> List[bytes]:
    """Return supported MAC algorithms"""

    return _mac_algs


def get_default_mac_algs() -> List[bytes]:
    """Return default MAC algorithms"""

    return _default_mac_algs


def get_mac_params(mac_alg: bytes) -> _MACParams:
    """Get parameters of a MAC algorithm

       This function returns the key and hash sizes of a MAC algorithm and
       whether or not to compute the MAC before or after encryption.

    """

    return _mac_params[mac_alg]


def get_mac(mac_alg: bytes, key: bytes) -> MAC:
    """Return a MAC handler

       This function returns a MAC object initialized with the specified
       key that can be used for data signing and verification.

    """

    handler, hash_size, args = _mac_handler[mac_alg]
    return handler(key, hash_size, *args)


_mac_algs_list: Tuple[_MACAlgsArgs, ...] = (
    (b'',                         0,  0, False, _NullMAC, (),         True),
)

if _umac_available: # pragma: no branch
    _mac_algs_list += (
        (b'umac-64' + _ETM,      16,  8, True,  _UMAC,    (umac64,),  True),
        (b'umac-128' + _ETM,     16, 16, True,  _UMAC,    (umac128,), True))

_mac_algs_list += (
    (b'hmac-sha2-256' + _ETM,    32, 32, True,  _HMAC,    (sha256,),  True),
    (b'hmac-sha2-512' + _ETM,    64, 64, True,  _HMAC,    (sha512,),  True),
    (b'hmac-sha1' + _ETM,        20, 20, True,  _HMAC,    (sha1,),    True),
    (b'hmac-md5' + _ETM,         16, 16, True,  _HMAC,    (md5,),     False),
    (b'hmac-sha2-256-96' + _ETM, 32, 12, True,  _HMAC,    (sha256,),  False),
    (b'hmac-sha2-512-96' + _ETM, 64, 12, True,  _HMAC,    (sha512,),  False),
    (b'hmac-sha1-96' + _ETM,     20, 12, True,  _HMAC,    (sha1,),    False),
    (b'hmac-md5-96' + _ETM,      16, 12, True,  _HMAC,    (md5,),     False))

if _umac_available: # pragma: no branch
    _mac_algs_list += (
        (b'umac-64' + _OPENSSH,  16,  8, False, _UMAC,    (umac64,),  True),
        (b'umac-128' + _OPENSSH, 16, 16, False, _UMAC,    (umac128,), True))

_mac_algs_list += (
    (b'hmac-sha2-256',           32, 32, False, _HMAC,    (sha256,),  True),
    (b'hmac-sha2-512',           64, 64, False, _HMAC,    (sha512,),  True),
    (b'hmac-sha1',               20, 20, False, _HMAC,    (sha1,),    True),
    (b'hmac-sha256-2@ssh.com',   32, 32, False, _HMAC,    (sha256,),  True),
    (b'hmac-sha224@ssh.com',     28, 28, False, _HMAC,    (sha224,),  True),
    (b'hmac-sha256@ssh.com',     16, 32, False, _HMAC,    (sha256,),  True),
    (b'hmac-sha384@ssh.com',     48, 48, False, _HMAC,    (sha384,),  True),
    (b'hmac-sha512@ssh.com',     64, 64, False, _HMAC,    (sha512,),  True),
    (b'hmac-md5',                16, 16, False, _HMAC,    (md5,),     False),
    (b'hmac-sha2-256-96',        32, 12, False, _HMAC,    (sha256,),  False),
    (b'hmac-sha2-512-96',        64, 12, False, _HMAC,    (sha512,),  False),
    (b'hmac-sha1-96',            20, 12, False, _HMAC,    (sha1,),    False),
    (b'hmac-md5-96',             16, 12, False, _HMAC,    (md5,),     False))

for _alg, _key_size, _hash_size, _etm, \
        _mac_alg, _args, _default in _mac_algs_list:
    register_mac_alg(_alg, _key_size, _hash_size, _etm,
                     _mac_alg, _args, _default)