Coverage for aiocoap/oscore_sitewrapper.py: 66%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

61 statements  

1# This file is part of the Python aiocoap library project. 

2# 

3# Copyright (c) 2012-2014 Maciej Wasilak <http://sixpinetrees.blogspot.com/>, 

4# 2013-2014 Christian Amsüss <c.amsuess@energyharvesting.at> 

5# 

6# aiocoap is free software, this file is published under the MIT license as 

7# described in the accompanying LICENSE file. 

8 

9"""This module assists in creating OSCORE servers by proving a wrapper around a 

10:class:aiocoap.resource.Site. It enforces no access control, but just indicates 

11to the resources whether a client is authenticated by setting the request's 

12remote property adaequately. 

13 

14So far, it needs to be utilized explicitly and manually at server creation. How 

15this will later be automated will depend on th edirection Site is going -- if 

16all of :meth:`aiocoap.protocol.Context.render_to_plumbing_request` can be moved 

17into Site wrappers, this can stay a site wrapper -- otherwise, it may need to 

18move in there to start render_to_plumbing_request on the unprotected requests 

19again. (This will also influence a future inner-blockwise implementation). 

20""" 

21 

22# WIP: TThis is being ported out of plugtest-server, leaving out the block-wise 

23# and observation parts for now. 

24 

25import logging 

26 

27import aiocoap 

28from aiocoap import interfaces 

29from aiocoap import oscore, error 

30 

31from aiocoap.transports.oscore import OSCOREAddress 

32 

33class OscoreSiteWrapper(interfaces.Resource): 

34 def __init__(self, inner_site, server_credentials): 

35 self.log = logging.getLogger('oscore-site') 

36 

37 self._inner_site = inner_site 

38 self.server_credentials = server_credentials 

39 

40 async def needs_blockwise_assembly(self, request): 

41 if not request.opt.object_security: 

42 return await self._inner_site.needs_blockwise_assembly(request) 

43 

44 # enable outer-blockwise 

45 return True 

46 

47 # FIXME: should there be a get_resources_as_linkheader that just forwards 

48 # all the others and indicates ;osc everywhere? 

49 

50 async def render(self, request): 

51 try: 

52 unprotected = oscore.verify_start(request) 

53 except oscore.NotAProtectedMessage: 

54 # ie. if no object_seccurity present 

55 return await self._inner_site.render(request) 

56 

57 try: 

58 sc = self.server_credentials.find_oscore(unprotected) 

59 except KeyError: 

60 if request.mtype == aiocoap.CON: 

61 raise error.Unauthorized("Security context not found") 

62 else: 

63 return aiocoap.message.NoResponse 

64 

65 try: 

66 unprotected, seqno = sc.unprotect(request) 

67 except error.RenderableError as e: 

68 # Primarily used for the Echo recovery 4.01 reply; the below could 

69 # be migrated there, but the behavior (at least as currently 

70 # encoded) is not exactly the one a no_response=26 would show, as 

71 # we want full responses to CONs but no responses to NONs, wheras 

72 # no_response=26 only flushes out an empty ACK and nothing more 

73 return e.to_message() 

74 except oscore.ReplayError: 

75 if request.mtype == aiocoap.CON: 

76 return aiocoap.Message(code=aiocoap.UNAUTHORIZED, max_age=0, payload=b"Replay detected") 

77 else: 

78 return aiocoap.message.NoResponse 

79 except oscore.DecodeError: 

80 if request.mtype == aiocoap.CON: 

81 raise error.BadOption("Failed to decode COSE") 

82 else: 

83 return aiocoap.message.NoResponse 

84 except oscore.ProtectionInvalid: 

85 if request.mtype == aiocoap.CON: 

86 raise error.BadRequest("Decryption failed") 

87 else: 

88 return aiocoap.message.NoResponse 

89 

90 unprotected.remote = OSCOREAddress(sc, request.remote) 

91 

92 self.log.debug("Request %r was unprotected into %r", request, unprotected) 

93 

94 sc = sc.context_for_response() 

95 

96 eventual_err = None 

97 try: 

98 response = await self._inner_site.render(unprotected) 

99 except error.RenderableError as err: 

100 try: 

101 response = err.to_message() 

102 except Exception as err: 

103 eventual_err = err 

104 except Exception as err: 

105 eventual_err = err 

106 if eventual_err is not None: 

107 response = aiocoap.Message(code=aiocoap.INTERNAL_SERVER_ERROR) 

108 self.log.error("An exception occurred while rendering a protected resource: %r", eventual_err, exc_info=eventual_err) 

109 

110 protected_response, _ = sc.protect(response, seqno) 

111 

112 self.log.debug("Response %r was encrypted into %r", response, protected_response) 

113 

114 return protected_response