Skip to content

Commit

Permalink
Add asn1crypto support
Browse files Browse the repository at this point in the history
Closes: #33
Signed-off-by: Christian Heimes <cheimes@redhat.com>
  • Loading branch information
tiran authored and frozencemetery committed Aug 8, 2018
1 parent 074fda5 commit 5df7db4
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 74 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ __pycache__
/MANIFEST
/*.egg-info
/.cache
/.coverage.*
16 changes: 10 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,20 @@ cache: pip
matrix:
include:
- python: 2.7
env: TOXENV=py27
- python: 3.4
env: TOXENV=py34
env: TOXENV=py27-asn1crypto
- python: 3.5
env: TOXENV=py35
env: TOXENV=py35-asn1crypto
- python: 3.6
env: TOXENV=py36
env: TOXENV=py36-asn1crypto
- python: 2.7
env: TOXENV=pep8
env: TOXENV=py27-pyasn1
- python: 3.5
env: TOXENV=py35-pyasn1
- python: 3.6
env: TOXENV=py36-pyasn1
- python: 2.7
env: TOXENV=pep8
- python: 3.6
env: TOXENV=py3pep8

install:
Expand Down
95 changes: 49 additions & 46 deletions kdcproxy/codec.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,35 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import os
import struct

from pyasn1 import error
from pyasn1.codec.der import decoder, encoder

import kdcproxy.asn1 as asn1


class ParsingError(Exception):

def __init__(self, message):
super(ParsingError, self).__init__(message)
self.message = message
from kdcproxy.exceptions import ParsingError

ASN1MOD = os.environ.get('KDCPROXY_ASN1MOD')

if ASN1MOD is None:
try:
from asn1crypto.version import __version_info__ as asn1crypto_version
except ImportError:
asn1crypto_version = None
else:
if asn1crypto_version >= (0, 22, 0):
ASN1MOD = 'asn1crypto'
if ASN1MOD is None:
try:
__import__('pyasn1')
except ImportError:
pass
else:
ASN1MOD = 'pyasn1'

if ASN1MOD == 'asn1crypto':
from kdcproxy import parse_asn1crypto as asn1mod
elif ASN1MOD == 'pyasn1':
from kdcproxy import parse_pyasn1 as asn1mod
else:
raise ValueError("Invalid KDCPROXY_ASN1MOD='{}'".format(ASN1MOD))


class ProxyRequest(object):
Expand All @@ -40,16 +56,7 @@ class ProxyRequest(object):

@classmethod
def parse(cls, data):
(req, err) = decoder.decode(data, asn1Spec=asn1.ProxyMessage())
if err:
raise ParsingError("Invalid request.")

request = req.getComponentByName('message').asOctets()
realm = req.getComponentByName('realm').asOctets()
try: # Python 3.x
realm = str(realm, "UTF8")
except TypeError: # Python 2.x
realm = str(realm)
request, realm, _ = asn1mod.decode_proxymessage(data)

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

for subcls in cls.__subclasses__():
try:
(req, err) = decoder.decode(request[subcls.OFFSET:],
asn1Spec=subcls.TYPE())
return subcls(realm, request, err)
except error.PyAsn1Error:
return subcls.parse_request(realm, request)
except ParsingError:
pass

raise ParsingError("Invalid request.")

def __init__(self, realm, request, err):
@classmethod
def parse_request(cls, realm, request):
pretty_name = asn1mod.try_decode(request[cls.OFFSET:], cls.TYPE)
return cls(realm, request, pretty_name)

def __init__(self, realm, request, pretty_name):
self.realm = realm
self.request = request

if len(err) > 0:
type = self.__class__.__name__[:0 - len(ProxyRequest.__name__)]
raise ParsingError("%s request has %d extra bytes." %
(type, len(err)))
self.pretty_name = pretty_name

def __str__(self):
type = self.__class__.__name__[:0 - len(ProxyRequest.__name__)]
return "%s %s-REQ (%d bytes)" % (self.realm, type,
len(self.request) - 4)
return "%s %s (%d bytes)" % (self.realm, self.pretty_name,
len(self.request) - 4)


class TGSProxyRequest(ProxyRequest):
TYPE = asn1.TGSREQ
TYPE = asn1mod.TGSREQ


class ASProxyRequest(ProxyRequest):
TYPE = asn1.ASREQ
TYPE = asn1mod.ASREQ


class KPASSWDProxyRequest(ProxyRequest):
TYPE = asn1.APREQ
TYPE = asn1mod.APREQ
OFFSET = 10

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

super(KPASSWDProxyRequest, self).__init__(realm, request, err)
self = cls(realm, request, "KPASSWD-REQ")
self.version = version
return self

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


def encode(data):
rep = asn1.ProxyMessage()
rep.setComponentByName('message', data)
return encoder.encode(rep)
return asn1mod.encode_proxymessage(data)
30 changes: 30 additions & 0 deletions kdcproxy/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright (C) 2017, Red Hat, Inc.
# All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.


class ParsingError(Exception):
def __init__(self, message):
super(ParsingError, self).__init__(message)
self.message = message


class ASN1ParsingError(ParsingError):
pass
104 changes: 104 additions & 0 deletions kdcproxy/parse_asn1crypto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Copyright (C) 2017, Red Hat, Inc.
# All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

from asn1crypto import core

from kdcproxy.exceptions import ASN1ParsingError


APPLICATION = 1


class KerberosString(core.GeneralString):
"""KerberosString ::= GeneralString (IA5String)
For compatibility, implementations MAY choose to accept GeneralString
values that contain characters other than those permitted by
IA5String...
"""


class Realm(KerberosString):
"""Realm ::= KerberosString
"""


class ProxyMessage(core.Sequence):
pretty_name = 'KDC-PROXY-MESSAGE'

_fields = [
('kerb-message', core.OctetString, {
'explicit': 0}),
('target-domain', Realm, {
'explicit': 1, 'optional': True}),
('dclocator-hint', core.Integer, {
'explicit': 2, 'optional': True}),
]


class ASREQ(core.Sequence):
pretty_name = 'AS-REQ'

explicit = (APPLICATION, 10)


class TGSREQ(core.Sequence):
pretty_name = 'TGS-REQ'

explicit = (APPLICATION, 12)


class APREQ(core.Sequence):
pretty_name = 'AP-REQ'

explicit = (APPLICATION, 14)


class KRBPriv(core.Sequence):
pretty_name = 'KRBPRiv'

explicit = (APPLICATION, 21)


def decode_proxymessage(data):
req = ProxyMessage.load(data, strict=True)
message = req['kerb-message'].native
realm = req['target-domain'].native
try: # Python 3.x
realm = str(realm, "utf-8")
except TypeError: # Python 2.x
realm = str(realm)
flags = req['dclocator-hint'].native
return message, realm, flags


def encode_proxymessage(data):
rep = ProxyMessage()
rep['kerb-message'] = data
return rep.dump()


def try_decode(data, cls):
try:
req = cls.load(data, strict=True)
except ValueError as e:
raise ASN1ParsingError(e)
return req.pretty_name
Loading

0 comments on commit 5df7db4

Please sign in to comment.