• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tlsfuzzer / tlslite-ng / 17615324909

10 Sep 2025 01:30PM UTC coverage: 76.448% (-6.9%) from 83.378%
17615324909

Pull #556

github

web-flow
Merge 281134490 into b0f903667
Pull Request #556: ML-DSA support

4073 of 5975 branches covered (68.17%)

Branch coverage included in aggregate %.

27 of 132 new or added lines in 9 files covered. (20.45%)

928 existing lines in 40 files now uncovered.

11378 of 14236 relevant lines covered (79.92%)

0.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.02
/tlslite/utils/python_key.py
1

2

3
from .python_rsakey import Python_RSAKey
1✔
4
from .python_ecdsakey import Python_ECDSAKey
1✔
5
from .python_dsakey import Python_DSAKey
1✔
6
from .python_eddsakey import Python_EdDSAKey
1✔
7
from .pem import dePem, pemSniff
1✔
8
from .asn1parser import ASN1Parser
1✔
9
from .cryptomath import bytesToNumber
1✔
10
from .compat import compatHMAC, ML_DSA_AVAILABLE
1✔
11
from ecdsa.curves import NIST256p, NIST384p, NIST521p
1✔
12
from ecdsa.keys import SigningKey, VerifyingKey
1✔
13
if ML_DSA_AVAILABLE:
1!
NEW
14
    from .python_mldsakey import Python_MLDSAKey
×
NEW
15
    from dilithium_py.ml_dsa.pkcs import sk_from_der
×
16

17
class Python_Key(object):
1✔
18
    """
19
    Generic methods for parsing private keys from files.
20

21
    Handles both RSA and ECDSA keys, irrespective of file format.
22
    """
23

24
    @staticmethod
1✔
25
    def parsePEM(s, passwordCallback=None):
1✔
26
        """Parse a string containing a PEM-encoded <privateKey>."""
27

28
        if pemSniff(s, "PRIVATE KEY"):
1✔
29
            bytes = dePem(s, "PRIVATE KEY")
1✔
30
            return Python_Key._parse_pkcs8(bytes)
1✔
31
        elif pemSniff(s, "RSA PRIVATE KEY"):
1✔
32
            bytes = dePem(s, "RSA PRIVATE KEY")
1✔
33
            return Python_Key._parse_ssleay(bytes, "rsa")
1✔
34
        elif pemSniff(s, "DSA PRIVATE KEY"):
1✔
35
            bytes = dePem(s, "DSA PRIVATE KEY")
1✔
36
            return Python_Key._parse_dsa_ssleay(bytes)
1✔
37
        elif pemSniff(s, "EC PRIVATE KEY"):
1✔
38
            bytes = dePem(s, "EC PRIVATE KEY")
1✔
39
            return Python_Key._parse_ecc_ssleay(bytes)
1✔
40
        elif pemSniff(s, "PUBLIC KEY"):
1!
41
            bytes = dePem(s, "PUBLIC KEY")
1✔
42
            return Python_Key._parse_public_key(bytes)
1✔
43
        else:
44
            raise SyntaxError("Not a PEM private key file")
×
45

46
    @staticmethod
1✔
47
    def _parse_public_key(bytes):
48
        # public keys are encoded as the subject_public_key_info objects
49
        spk_info = ASN1Parser(bytes)
1✔
50

51
        # first element of the SEQUENCE is the AlgorithmIdentifier
52
        alg_id = spk_info.getChild(0)
1✔
53

54
        # AlgId has two elements, the OID of the algorithm and parameters
55
        # parameters generally have to be NULL, with exception of RSA-PSS
56

57
        alg_oid = alg_id.getChild(0)
1✔
58

59
        if list(alg_oid.value) == [42, 134, 72, 134, 247, 13, 1, 1, 1]:
1✔
60
            key_type = "rsa"
1✔
61
        elif list(alg_oid.value) == [42, 134, 72, 206, 56, 4, 1]:
1!
62
            key_type = "dsa"
1✔
63
        else:
64
            raise SyntaxError("Only RSA or DSA Public keys supported")
×
65

66
        if key_type == "rsa":
1✔
67
            subject_public_key = ASN1Parser(
1✔
68
                ASN1Parser(spk_info.getChildBytes(1)).value[1:])
69

70
            modulus = subject_public_key.getChild(0)
1✔
71
            exponent = subject_public_key.getChild(1)
1✔
72

73
            n = bytesToNumber(modulus.value)
1✔
74
            e = bytesToNumber(exponent.value)
1✔
75

76
            return Python_RSAKey(n, e, key_type="rsa")
1✔
77

78
        elif key_type == "dsa":
1!
79
            # public key
80
            subject_public_key = ASN1Parser(
1✔
81
                ASN1Parser(spk_info.getChildBytes(1)).value[1:])
82

83
            public_key = bytesToNumber(subject_public_key.value)
1✔
84

85
            # domain parameters
86
            domain = alg_id.getChild(1)
1✔
87

88
            p = bytesToNumber(domain.getChild(0).value)
1✔
89
            q = bytesToNumber(domain.getChild(1).value)
1✔
90
            g = bytesToNumber(domain.getChild(2).value)
1✔
91

92
            return Python_DSAKey(p, q, g, y=public_key)
1✔
93

94
    @staticmethod
1✔
95
    def _parse_pkcs8(bytes):
96
        parser = ASN1Parser(bytes)
1✔
97

98
        # first element in PrivateKeyInfo is an INTEGER
99
        version = parser.getChild(0).value
1✔
100
        if bytesToNumber(version) != 0:
1!
101
            raise SyntaxError("Unrecognized PKCS8 version")
×
102

103
        # second element in PrivateKeyInfo is a SEQUENCE of type
104
        # AlgorithmIdentifier
105
        alg_ident = parser.getChild(1)
1✔
106
        seq_len = alg_ident.getChildCount()
1✔
107
        # first item of AlgorithmIdentifier is an OBJECT (OID)
108
        oid = alg_ident.getChild(0)
1✔
109
        if list(oid.value) == [42, 134, 72, 134, 247, 13, 1, 1, 1]:
1✔
110
            key_type = "rsa"
1✔
111
        elif list(oid.value) == [42, 134, 72, 134, 247, 13, 1, 1, 10]:
1✔
112
            key_type = "rsa-pss"
1✔
113
        elif list(oid.value) == [42, 134, 72, 206, 56, 4, 1]:
1✔
114
            key_type = "dsa"
1✔
115
        elif list(oid.value) == [42, 134, 72, 206, 61, 2, 1]:
1✔
116
            key_type = "ecdsa"
1✔
117
        elif list(oid.value) == [43, 101, 112]:
1✔
118
            key_type = "Ed25519"
1✔
119
        elif list(oid.value) == [43, 101, 113]:
1!
120
            key_type = "Ed448"
1✔
NEW
121
        elif list(oid.value) == [96, 134, 72, 1, 101, 3, 4, 3, 17]:
×
NEW
122
            key_type = "mldsa44"
×
NEW
123
        elif list(oid.value) == [96, 134, 72, 1, 101, 3, 4, 3, 18]:
×
NEW
124
            key_type = "mldsa65"
×
NEW
125
        elif list(oid.value) == [96, 134, 72, 1, 101, 3, 4, 3, 19]:
×
NEW
126
            key_type = "mldsa87"
×
127
        else:
128
            raise SyntaxError("Unrecognized AlgorithmIdentifier: {0}"
×
129
                              .format(list(oid.value)))
130
        # second item of AlgorithmIdentifier are parameters (defined by
131
        # above algorithm)
132
        if key_type == "rsa":
1✔
133
            if seq_len != 2:
1!
134
                raise SyntaxError("Missing parameters for RSA algorithm ID")
×
135
            parameters = alg_ident.getChild(1)
1✔
136
            if parameters.value != bytearray(0):
1!
137
                raise SyntaxError("RSA parameters are not NULL")
×
138
        if key_type == "dsa":
1✔
139
            if seq_len != 2:
1!
140
                raise SyntaxError("Invalid encoding of algorithm identifier")
×
141
            parameters = alg_ident.getChild(1)
1✔
142
            if parameters.value == bytearray(0):
1!
143
                parameters = None
×
144
        elif key_type == "ecdsa":
1✔
145
            if seq_len != 2:
1!
146
                raise SyntaxError("Invalid encoding of algorithm identifier")
×
147
            curveID = alg_ident.getChild(1)
1✔
148
            if list(curveID.value) == [42, 134, 72, 206, 61, 3, 1, 7]:
1✔
149
                curve = NIST256p
1✔
150
            elif list(curveID.value) == [43, 129, 4, 0, 34]:
1✔
151
                curve = NIST384p
1✔
152
            elif list(curveID.value) == [43, 129, 4, 0, 35]:
1✔
153
                curve = NIST521p
1✔
154
            else:
155
                raise SyntaxError("Unknown curve")
1✔
156
        else:  # rsa-pss or ML-DSA
157
            pass  # ignore parameters - don't apply restrictions
158

159
        if seq_len > 2:
1!
160
            raise SyntaxError("Invalid encoding of AlgorithmIdentifier")
×
161

162
        #Get the privateKey
163
        private_key_parser = parser.getChild(2)
1✔
164

165
        #Adjust for OCTET STRING encapsulation
166
        private_key_parser = ASN1Parser(private_key_parser.value)
1✔
167

168
        if key_type in ("Ed25519", "Ed448"):
1✔
169
            return Python_Key._parse_eddsa_private_key(bytes)
1✔
170
        if key_type == "ecdsa":
1✔
171
            return Python_Key._parse_ecdsa_private_key(private_key_parser,
1✔
172
                                                       curve)
173
        elif key_type == "dsa":
1✔
174
            return Python_Key._parse_dsa_private_key(private_key_parser, parameters)
1✔
175
        elif key_type in ("mldsa44", "mldsa65", "mldsa87"):
1!
NEW
176
            return Python_Key._parse_mldsa_private_key(bytes)
×
177
        else:
178
            return Python_Key._parse_asn1_private_key(private_key_parser,
1✔
179
                                                      key_type)
180

181
    @staticmethod
1✔
182
    def _parse_ssleay(data, key_type="rsa"):
1✔
183
        """
184
        Parse binary structure of the old SSLeay file format used by OpenSSL.
185

186
        For RSA keys.
187
        """
188
        private_key_parser = ASN1Parser(data)
1✔
189
        # "rsa" type as old format doesn't support rsa-pss parameters
190
        return Python_Key._parse_asn1_private_key(private_key_parser, key_type)
1✔
191

192
    @staticmethod
1✔
193
    def _parse_dsa_ssleay(data):
194
        """
195
        Parse binary structure of the old SSLeay file format used by OpenSSL.
196

197
        For DSA keys.
198
        """
199
        private_key_parser = ASN1Parser(data)
1✔
200
        return Python_Key._parse_dsa_private_key(private_key_parser)
1✔
201

202
    @staticmethod
1✔
203
    def _parse_ecc_ssleay(data):
204
        """
205
        Parse binary structure of the old SSLeay file format used by OpenSSL.
206

207
        For ECDSA keys.
208
        """
209
        private_key = SigningKey.from_der(compatHMAC(data))
1✔
210
        secret_mult = private_key.privkey.secret_multiplier
1✔
211
        return Python_ECDSAKey(None, None, private_key.curve.name,
1✔
212
                               secret_mult)
213

214
    @staticmethod
1✔
215
    def _parse_ecdsa_private_key(private, curve):
216
        ver = private.getChild(0)
1✔
217
        if ver.value != b'\x01':
1!
218
            raise SyntaxError("Unexpected EC key version")
×
219
        private_key = private.getChild(1)
1✔
220
        public_key = private.getChild(2)
1✔
221
        # first two bytes are the ASN.1 custom type and the length of payload
222
        # while the latter two bytes are just specification of the public
223
        # key encoding (uncompressed)
224
        # TODO: update ecdsa lib to be able to parse PKCS#8 files
225
        if curve is not NIST521p:
1✔
226
            if list(public_key.value[:1]) != [3] or \
1!
227
                    list(public_key.value[2:4]) != [0, 4]:
228
                raise SyntaxError("Invalid or unsupported encoding of public key")
×
229

230
            pub_key = VerifyingKey.from_string(
1✔
231
                    compatHMAC(public_key.value[4:]),
232
                    curve)
233
        else:
234
            if list(public_key.value[:3]) != [3, 129, 134] or \
1!
235
                    list(public_key.value[3:5]) != [0, 4]:
236
                raise SyntaxError("Invalid or unsupported encoding of public key")
×
237

238
            pub_key = VerifyingKey.from_string(
1✔
239
                    compatHMAC(public_key.value[5:]),
240
                    curve)
241
        pub_x = pub_key.pubkey.point.x()
1✔
242
        pub_y = pub_key.pubkey.point.y()
1✔
243
        priv_key = SigningKey.from_string(compatHMAC(private_key.value),
1✔
244
                                          curve)
245
        mult = priv_key.privkey.secret_multiplier
1✔
246
        return Python_ECDSAKey(pub_x, pub_y, curve.name, mult)
1✔
247

248
    @staticmethod
1✔
249
    def _parse_eddsa_private_key(data):
250
        """Parse a DER encoded EdDSA key."""
251
        priv_key = SigningKey.from_der(data)
1✔
252
        return Python_EdDSAKey(priv_key.verifying_key, private_key=priv_key)
1✔
253

254
    @staticmethod
1✔
255
    def _parse_mldsa_private_key(data):
256
        """Parse a DER encoded ML-DSA key."""
NEW
257
        ml_dsa, priv_key, _, pub_key = sk_from_der(data)
×
NEW
258
        return Python_MLDSAKey((ml_dsa, pub_key), (ml_dsa, priv_key))
×
259

260
    @staticmethod
1✔
261
    def _parse_asn1_private_key(private_key_parser, key_type):
262
        version = private_key_parser.getChild(0).value[0]
1✔
263
        if version != 0:
1!
264
            raise SyntaxError("Unrecognized RSAPrivateKey version")
×
265
        n = bytesToNumber(private_key_parser.getChild(1).value)
1✔
266
        e = bytesToNumber(private_key_parser.getChild(2).value)
1✔
267
        d = bytesToNumber(private_key_parser.getChild(3).value)
1✔
268
        p = bytesToNumber(private_key_parser.getChild(4).value)
1✔
269
        q = bytesToNumber(private_key_parser.getChild(5).value)
1✔
270
        dP = bytesToNumber(private_key_parser.getChild(6).value)
1✔
271
        dQ = bytesToNumber(private_key_parser.getChild(7).value)
1✔
272
        qInv = bytesToNumber(private_key_parser.getChild(8).value)
1✔
273
        return Python_RSAKey(n, e, d, p, q, dP, dQ, qInv, key_type)
1✔
274

275

276
    @staticmethod
1✔
277
    def _parse_dsa_private_key(private_key_parser, domain_parameters=None):
1✔
278
        if domain_parameters:
1✔
279
            p = bytesToNumber(domain_parameters.getChild(0).value)
1✔
280
            q = bytesToNumber(domain_parameters.getChild(1).value)
1✔
281
            g = bytesToNumber(domain_parameters.getChild(2).value)
1✔
282
            x = bytesToNumber(private_key_parser.value)
1✔
283
            return Python_DSAKey(p, q, g, x)
1✔
284
        p = bytesToNumber(private_key_parser.getChild(1).value)
1✔
285
        q = bytesToNumber(private_key_parser.getChild(2).value)
1✔
286
        g = bytesToNumber(private_key_parser.getChild(3).value)
1✔
287
        y = bytesToNumber(private_key_parser.getChild(4).value)
1✔
288
        x = bytesToNumber(private_key_parser.getChild(5).value)
1✔
289
        return Python_DSAKey(p, q, g, x, y)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc