Skip to content

Commit 5df7db4

Browse files
tiranfrozencemetery
authored andcommitted
Add asn1crypto support
Closes: #33 Signed-off-by: Christian Heimes <cheimes@redhat.com>
1 parent 074fda5 commit 5df7db4

9 files changed

Lines changed: 296 additions & 74 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ __pycache__
77
/MANIFEST
88
/*.egg-info
99
/.cache
10+
/.coverage.*

.travis.yml

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,20 @@ cache: pip
77
matrix:
88
include:
99
- python: 2.7
10-
env: TOXENV=py27
11-
- python: 3.4
12-
env: TOXENV=py34
10+
env: TOXENV=py27-asn1crypto
1311
- python: 3.5
14-
env: TOXENV=py35
12+
env: TOXENV=py35-asn1crypto
1513
- python: 3.6
16-
env: TOXENV=py36
14+
env: TOXENV=py36-asn1crypto
1715
- python: 2.7
18-
env: TOXENV=pep8
16+
env: TOXENV=py27-pyasn1
1917
- python: 3.5
18+
env: TOXENV=py35-pyasn1
19+
- python: 3.6
20+
env: TOXENV=py36-pyasn1
21+
- python: 2.7
22+
env: TOXENV=pep8
23+
- python: 3.6
2024
env: TOXENV=py3pep8
2125

2226
install:

kdcproxy/codec.py

Lines changed: 49 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,35 @@
1919
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2020
# THE SOFTWARE.
2121

22+
import os
2223
import struct
2324

24-
from pyasn1 import error
25-
from pyasn1.codec.der import decoder, encoder
26-
27-
import kdcproxy.asn1 as asn1
28-
29-
30-
class ParsingError(Exception):
31-
32-
def __init__(self, message):
33-
super(ParsingError, self).__init__(message)
34-
self.message = message
25+
from kdcproxy.exceptions import ParsingError
26+
27+
ASN1MOD = os.environ.get('KDCPROXY_ASN1MOD')
28+
29+
if ASN1MOD is None:
30+
try:
31+
from asn1crypto.version import __version_info__ as asn1crypto_version
32+
except ImportError:
33+
asn1crypto_version = None
34+
else:
35+
if asn1crypto_version >= (0, 22, 0):
36+
ASN1MOD = 'asn1crypto'
37+
if ASN1MOD is None:
38+
try:
39+
__import__('pyasn1')
40+
except ImportError:
41+
pass
42+
else:
43+
ASN1MOD = 'pyasn1'
44+
45+
if ASN1MOD == 'asn1crypto':
46+
from kdcproxy import parse_asn1crypto as asn1mod
47+
elif ASN1MOD == 'pyasn1':
48+
from kdcproxy import parse_pyasn1 as asn1mod
49+
else:
50+
raise ValueError("Invalid KDCPROXY_ASN1MOD='{}'".format(ASN1MOD))
3551

3652

3753
class ProxyRequest(object):
@@ -40,16 +56,7 @@ class ProxyRequest(object):
4056

4157
@classmethod
4258
def parse(cls, data):
43-
(req, err) = decoder.decode(data, asn1Spec=asn1.ProxyMessage())
44-
if err:
45-
raise ParsingError("Invalid request.")
46-
47-
request = req.getComponentByName('message').asOctets()
48-
realm = req.getComponentByName('realm').asOctets()
49-
try: # Python 3.x
50-
realm = str(realm, "UTF8")
51-
except TypeError: # Python 2.x
52-
realm = str(realm)
59+
request, realm, _ = asn1mod.decode_proxymessage(data)
5360

5461
# Check the length of the whole request message.
5562
(length, ) = struct.unpack("!I", request[0:4])
@@ -58,42 +65,41 @@ def parse(cls, data):
5865

5966
for subcls in cls.__subclasses__():
6067
try:
61-
(req, err) = decoder.decode(request[subcls.OFFSET:],
62-
asn1Spec=subcls.TYPE())
63-
return subcls(realm, request, err)
64-
except error.PyAsn1Error:
68+
return subcls.parse_request(realm, request)
69+
except ParsingError:
6570
pass
6671

6772
raise ParsingError("Invalid request.")
6873

69-
def __init__(self, realm, request, err):
74+
@classmethod
75+
def parse_request(cls, realm, request):
76+
pretty_name = asn1mod.try_decode(request[cls.OFFSET:], cls.TYPE)
77+
return cls(realm, request, pretty_name)
78+
79+
def __init__(self, realm, request, pretty_name):
7080
self.realm = realm
7181
self.request = request
72-
73-
if len(err) > 0:
74-
type = self.__class__.__name__[:0 - len(ProxyRequest.__name__)]
75-
raise ParsingError("%s request has %d extra bytes." %
76-
(type, len(err)))
82+
self.pretty_name = pretty_name
7783

7884
def __str__(self):
79-
type = self.__class__.__name__[:0 - len(ProxyRequest.__name__)]
80-
return "%s %s-REQ (%d bytes)" % (self.realm, type,
81-
len(self.request) - 4)
85+
return "%s %s (%d bytes)" % (self.realm, self.pretty_name,
86+
len(self.request) - 4)
8287

8388

8489
class TGSProxyRequest(ProxyRequest):
85-
TYPE = asn1.TGSREQ
90+
TYPE = asn1mod.TGSREQ
8691

8792

8893
class ASProxyRequest(ProxyRequest):
89-
TYPE = asn1.ASREQ
94+
TYPE = asn1mod.ASREQ
9095

9196

9297
class KPASSWDProxyRequest(ProxyRequest):
93-
TYPE = asn1.APREQ
98+
TYPE = asn1mod.APREQ
9499
OFFSET = 10
95100

96-
def __init__(self, realm, request, err):
101+
@classmethod
102+
def parse_request(cls, realm, request):
97103
# Check the length count in the password change request, assuming it
98104
# actually is a password change request. It should be the length of
99105
# the rest of the request, including itself.
@@ -118,13 +124,12 @@ def __init__(self, realm, request, err):
118124
# See if the tag looks like an AP request, which would look like the
119125
# start of a password change request. The rest of it should be a
120126
# KRB-PRIV message.
121-
(apreq, err) = decoder.decode(request[10:length + 10],
122-
asn1Spec=asn1.APREQ())
123-
(krbpriv, err) = decoder.decode(request[length + 10:],
124-
asn1Spec=asn1.KRBPriv())
127+
asn1mod.try_decode(request[10:length + 10], asn1mod.APREQ)
128+
asn1mod.try_decode(request[length + 10:], asn1mod.KRBPriv)
125129

126-
super(KPASSWDProxyRequest, self).__init__(realm, request, err)
130+
self = cls(realm, request, "KPASSWD-REQ")
127131
self.version = version
132+
return self
128133

129134
def __str__(self):
130135
tmp = super(KPASSWDProxyRequest, self).__str__()
@@ -137,6 +142,4 @@ def decode(data):
137142

138143

139144
def encode(data):
140-
rep = asn1.ProxyMessage()
141-
rep.setComponentByName('message', data)
142-
return encoder.encode(rep)
145+
return asn1mod.encode_proxymessage(data)

kdcproxy/exceptions.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Copyright (C) 2017, Red Hat, Inc.
2+
# All rights reserved.
3+
#
4+
# Permission is hereby granted, free of charge, to any person obtaining a copy
5+
# of this software and associated documentation files (the "Software"), to deal
6+
# in the Software without restriction, including without limitation the rights
7+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
# copies of the Software, and to permit persons to whom the Software is
9+
# furnished to do so, subject to the following conditions:
10+
#
11+
# The above copyright notice and this permission notice shall be included in
12+
# all copies or substantial portions of the Software.
13+
#
14+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
# THE SOFTWARE.
21+
22+
23+
class ParsingError(Exception):
24+
def __init__(self, message):
25+
super(ParsingError, self).__init__(message)
26+
self.message = message
27+
28+
29+
class ASN1ParsingError(ParsingError):
30+
pass

kdcproxy/parse_asn1crypto.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Copyright (C) 2017, Red Hat, Inc.
2+
# All rights reserved.
3+
#
4+
# Permission is hereby granted, free of charge, to any person obtaining a copy
5+
# of this software and associated documentation files (the "Software"), to deal
6+
# in the Software without restriction, including without limitation the rights
7+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
# copies of the Software, and to permit persons to whom the Software is
9+
# furnished to do so, subject to the following conditions:
10+
#
11+
# The above copyright notice and this permission notice shall be included in
12+
# all copies or substantial portions of the Software.
13+
#
14+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
# THE SOFTWARE.
21+
22+
from asn1crypto import core
23+
24+
from kdcproxy.exceptions import ASN1ParsingError
25+
26+
27+
APPLICATION = 1
28+
29+
30+
class KerberosString(core.GeneralString):
31+
"""KerberosString ::= GeneralString (IA5String)
32+
33+
For compatibility, implementations MAY choose to accept GeneralString
34+
values that contain characters other than those permitted by
35+
IA5String...
36+
"""
37+
38+
39+
class Realm(KerberosString):
40+
"""Realm ::= KerberosString
41+
"""
42+
43+
44+
class ProxyMessage(core.Sequence):
45+
pretty_name = 'KDC-PROXY-MESSAGE'
46+
47+
_fields = [
48+
('kerb-message', core.OctetString, {
49+
'explicit': 0}),
50+
('target-domain', Realm, {
51+
'explicit': 1, 'optional': True}),
52+
('dclocator-hint', core.Integer, {
53+
'explicit': 2, 'optional': True}),
54+
]
55+
56+
57+
class ASREQ(core.Sequence):
58+
pretty_name = 'AS-REQ'
59+
60+
explicit = (APPLICATION, 10)
61+
62+
63+
class TGSREQ(core.Sequence):
64+
pretty_name = 'TGS-REQ'
65+
66+
explicit = (APPLICATION, 12)
67+
68+
69+
class APREQ(core.Sequence):
70+
pretty_name = 'AP-REQ'
71+
72+
explicit = (APPLICATION, 14)
73+
74+
75+
class KRBPriv(core.Sequence):
76+
pretty_name = 'KRBPRiv'
77+
78+
explicit = (APPLICATION, 21)
79+
80+
81+
def decode_proxymessage(data):
82+
req = ProxyMessage.load(data, strict=True)
83+
message = req['kerb-message'].native
84+
realm = req['target-domain'].native
85+
try: # Python 3.x
86+
realm = str(realm, "utf-8")
87+
except TypeError: # Python 2.x
88+
realm = str(realm)
89+
flags = req['dclocator-hint'].native
90+
return message, realm, flags
91+
92+
93+
def encode_proxymessage(data):
94+
rep = ProxyMessage()
95+
rep['kerb-message'] = data
96+
return rep.dump()
97+
98+
99+
def try_decode(data, cls):
100+
try:
101+
req = cls.load(data, strict=True)
102+
except ValueError as e:
103+
raise ASN1ParsingError(e)
104+
return req.pretty_name

0 commit comments

Comments
 (0)