From 20c2b8986df3451d9d1f9e45a266731805f564b6 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 22 Mar 2013 00:14:57 +0900 Subject: fix import --- src/leap/common/testing/test_basetest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/leap/common/testing/test_basetest.py b/src/leap/common/testing/test_basetest.py index c4636df..220e28d 100644 --- a/src/leap/common/testing/test_basetest.py +++ b/src/leap/common/testing/test_basetest.py @@ -25,7 +25,7 @@ except ImportError: import os import StringIO -from leap.testing.basetest import BaseLeapTest +from leap.common.testing.basetest import BaseLeapTest _tempdir = None # global for tempdir checking -- cgit v1.2.3 From 19c6818d679353ddc7b7d7ffbad8fc497ff3b4f4 Mon Sep 17 00:00:00 2001 From: drebs Date: Sun, 31 Mar 2013 21:01:06 -0300 Subject: Fix and complement .gitignore file. --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index dabc057..331608c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ -*.pyc +*.pyc +*.egg *.egg-info +dist/ +build/ -- cgit v1.2.3 From 58e6a1df85204ab30b9a9351de32ac2f71f1026b Mon Sep 17 00:00:00 2001 From: drebs Date: Sun, 31 Mar 2013 21:02:23 -0300 Subject: Add events mechanism dependencies to setup.py file. --- setup.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b29c352..c25693b 100644 --- a/setup.py +++ b/setup.py @@ -19,9 +19,18 @@ setup file for leap.common """ from setuptools import setup, find_packages + requirements = [ + 'protobuf', + 'protobuf.socketrpc', ] + +dependency_links = [ + 'https://protobuf-socket-rpc.googlecode.com/files/protobuf.socketrpc-1.3.2-py2.6.egg#egg=protobuf.socketrpc', +] + + # XXX add classifiers, docs setup( @@ -39,5 +48,6 @@ setup( package_dir={'': 'src'}, packages=find_packages('src'), #test_suite='leap.common.tests', - #install_requires=requirements, + install_requires=requirements, + dependency_links=dependency_links, ) -- cgit v1.2.3 From 71d23cde2924b76ef9ec7922e75f040c5e3335fc Mon Sep 17 00:00:00 2001 From: drebs Date: Sun, 31 Mar 2013 21:02:50 -0300 Subject: Add protobuf definition for events messages and services. --- src/leap/common/events/Makefile | 31 +++ src/leap/common/events/events.proto | 69 +++++++ src/leap/common/events/events_pb2.py | 364 +++++++++++++++++++++++++++++++++++ 3 files changed, 464 insertions(+) create mode 100644 src/leap/common/events/Makefile create mode 100644 src/leap/common/events/events.proto create mode 100644 src/leap/common/events/events_pb2.py diff --git a/src/leap/common/events/Makefile b/src/leap/common/events/Makefile new file mode 100644 index 0000000..4f73dea --- /dev/null +++ b/src/leap/common/events/Makefile @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Makefile +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# This file is used to generate protobuf python files that are used for IPC: +# +# https://developers.google.com/protocol-buffers/docs/pythontutorial + +PROTOC = protoc + +all: events_pb2.py + +%_pb2.py: %.proto + $(PROTOC) --python_out=./ $< + autopep8 --in-place --aggressive $@ + +clean: + rm -f *_pb2.py diff --git a/src/leap/common/events/events.proto b/src/leap/common/events/events.proto new file mode 100644 index 0000000..29388b8 --- /dev/null +++ b/src/leap/common/events/events.proto @@ -0,0 +1,69 @@ +// signal.proto +// Copyright (C) 2013 LEA +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package leap.common.events; + +enum Event { + CLIENT_SESSION_ID = 1; + CLIENT_UID = 2; + SOLEDAD_CREATING_KEYS = 3; + SOLEDAD_DONE_CREATING_KEYS = 4; + SOLEDAD_UPLOADING_KEYS = 5; + SOLEDAD_DONE_UPLOADING_KEYS = 6; + SOLEDAD_DOWNLOADING_KEYS = 7; + SOLEDAD_DONE_DOWNLOADING_KEYS = 8; + SOLEDAD_NEW_DATA_TO_SYNC = 9; + SOLEDAD_DONE_DATA_SYNC = 10; + UPDATER_NEW_UPDATES = 11; + UPDATER_DONE_UPDATING = 12; +} + +message SignalRequest { + required Event event = 1; + required string content = 2; + required string mac_method = 3; + required bytes mac = 4; + optional string enc_method = 5; + optional bool error_occurred = 6; +} + +message RegisterRequest { + required Event event = 1; + required int32 port = 2; + required string mac_method = 3; + required bytes mac = 4; +} + +message EventResponse { + + enum Status { + OK = 1; + UNAUTH = 2; + ERROR = 3; + } + + required Status status = 1; + optional string result = 2; +} + +service EventsServerService { + rpc register(RegisterRequest) returns (EventResponse); + rpc signal(SignalRequest) returns (EventResponse); +} + +service EventsComponentService { + rpc signal(SignalRequest) returns (EventResponse); +} diff --git a/src/leap/common/events/events_pb2.py b/src/leap/common/events/events_pb2.py new file mode 100644 index 0000000..1d278cc --- /dev/null +++ b/src/leap/common/events/events_pb2.py @@ -0,0 +1,364 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! + +from google.protobuf import descriptor +from google.protobuf import message +from google.protobuf import reflection +from google.protobuf import service +from google.protobuf import service_reflection +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + + +DESCRIPTOR = descriptor.FileDescriptor( + name='events.proto', + package='leap.common.events', + serialized_pb='\n\x0c\x65vents.proto\x12\x12leap.common.events\"\x97\x01\n\rSignalRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0f\n\x07\x63ontent\x18\x02 \x02(\t\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\x12\x12\n\nenc_method\x18\x05 \x01(\t\x12\x16\n\x0e\x65rror_occurred\x18\x06 \x01(\x08\"j\n\x0fRegisterRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0c\n\x04port\x18\x02 \x02(\x05\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\"\x82\x01\n\rEventResponse\x12\x38\n\x06status\x18\x01 \x02(\x0e\x32(.leap.common.events.EventResponse.Status\x12\x0e\n\x06result\x18\x02 \x01(\t\"\'\n\x06Status\x12\x06\n\x02OK\x10\x01\x12\n\n\x06UNAUTH\x10\x02\x12\t\n\x05\x45RROR\x10\x03*\xd5\x02\n\x05\x45vent\x12\x15\n\x11\x43LIENT_SESSION_ID\x10\x01\x12\x0e\n\nCLIENT_UID\x10\x02\x12\x19\n\x15SOLEDAD_CREATING_KEYS\x10\x03\x12\x1e\n\x1aSOLEDAD_DONE_CREATING_KEYS\x10\x04\x12\x1a\n\x16SOLEDAD_UPLOADING_KEYS\x10\x05\x12\x1f\n\x1bSOLEDAD_DONE_UPLOADING_KEYS\x10\x06\x12\x1c\n\x18SOLEDAD_DOWNLOADING_KEYS\x10\x07\x12!\n\x1dSOLEDAD_DONE_DOWNLOADING_KEYS\x10\x08\x12\x1c\n\x18SOLEDAD_NEW_DATA_TO_SYNC\x10\t\x12\x1a\n\x16SOLEDAD_DONE_DATA_SYNC\x10\n\x12\x17\n\x13UPDATER_NEW_UPDATES\x10\x0b\x12\x19\n\x15UPDATER_DONE_UPDATING\x10\x0c\x32\xb9\x01\n\x13\x45ventsServerService\x12R\n\x08register\x12#.leap.common.events.RegisterRequest\x1a!.leap.common.events.EventResponse\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponse2h\n\x16\x45ventsComponentService\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponse') + +_EVENT = descriptor.EnumDescriptor( + name='Event', + full_name='leap.common.events.Event', + filename=None, + file=DESCRIPTOR, + values=[ + descriptor.EnumValueDescriptor( + name='CLIENT_SESSION_ID', index=0, number=1, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='CLIENT_UID', index=1, number=2, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_CREATING_KEYS', index=2, number=3, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_DONE_CREATING_KEYS', index=3, number=4, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_UPLOADING_KEYS', index=4, number=5, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_DONE_UPLOADING_KEYS', index=5, number=6, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_DOWNLOADING_KEYS', index=6, number=7, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_DONE_DOWNLOADING_KEYS', index=7, number=8, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_NEW_DATA_TO_SYNC', index=8, number=9, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_DONE_DATA_SYNC', index=9, number=10, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='UPDATER_NEW_UPDATES', index=10, number=11, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='UPDATER_DONE_UPDATING', index=11, number=12, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=432, + serialized_end=773, +) + + +CLIENT_SESSION_ID = 1 +CLIENT_UID = 2 +SOLEDAD_CREATING_KEYS = 3 +SOLEDAD_DONE_CREATING_KEYS = 4 +SOLEDAD_UPLOADING_KEYS = 5 +SOLEDAD_DONE_UPLOADING_KEYS = 6 +SOLEDAD_DOWNLOADING_KEYS = 7 +SOLEDAD_DONE_DOWNLOADING_KEYS = 8 +SOLEDAD_NEW_DATA_TO_SYNC = 9 +SOLEDAD_DONE_DATA_SYNC = 10 +UPDATER_NEW_UPDATES = 11 +UPDATER_DONE_UPDATING = 12 + + +_EVENTRESPONSE_STATUS = descriptor.EnumDescriptor( + name='Status', + full_name='leap.common.events.EventResponse.Status', + filename=None, + file=DESCRIPTOR, + values=[ + descriptor.EnumValueDescriptor( + name='OK', index=0, number=1, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='UNAUTH', index=1, number=2, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='ERROR', index=2, number=3, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=390, + serialized_end=429, +) + + +_SIGNALREQUEST = descriptor.Descriptor( + name='SignalRequest', + full_name='leap.common.events.SignalRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + descriptor.FieldDescriptor( + name='event', full_name='leap.common.events.SignalRequest.event', index=0, + number=1, type=14, cpp_type=8, label=2, + has_default_value=False, default_value=1, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + descriptor.FieldDescriptor( + name='content', full_name='leap.common.events.SignalRequest.content', index=1, + number=2, type=9, cpp_type=9, label=2, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + descriptor.FieldDescriptor( + name='mac_method', full_name='leap.common.events.SignalRequest.mac_method', index=2, + number=3, type=9, cpp_type=9, label=2, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + descriptor.FieldDescriptor( + name='mac', full_name='leap.common.events.SignalRequest.mac', index=3, + number=4, type=12, cpp_type=9, label=2, + has_default_value=False, default_value="", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + descriptor.FieldDescriptor( + name='enc_method', full_name='leap.common.events.SignalRequest.enc_method', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + descriptor.FieldDescriptor( + name='error_occurred', full_name='leap.common.events.SignalRequest.error_occurred', index=5, + number=6, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + extension_ranges=[], + serialized_start=37, + serialized_end=188, +) + + +_REGISTERREQUEST = descriptor.Descriptor( + name='RegisterRequest', + full_name='leap.common.events.RegisterRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + descriptor.FieldDescriptor( + name='event', full_name='leap.common.events.RegisterRequest.event', index=0, + number=1, type=14, cpp_type=8, label=2, + has_default_value=False, default_value=1, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + descriptor.FieldDescriptor( + name='port', full_name='leap.common.events.RegisterRequest.port', index=1, + number=2, type=5, cpp_type=1, label=2, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + descriptor.FieldDescriptor( + name='mac_method', full_name='leap.common.events.RegisterRequest.mac_method', index=2, + number=3, type=9, cpp_type=9, label=2, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + descriptor.FieldDescriptor( + name='mac', full_name='leap.common.events.RegisterRequest.mac', index=3, + number=4, type=12, cpp_type=9, label=2, + has_default_value=False, default_value="", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + extension_ranges=[], + serialized_start=190, + serialized_end=296, +) + + +_EVENTRESPONSE = descriptor.Descriptor( + name='EventResponse', + full_name='leap.common.events.EventResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + descriptor.FieldDescriptor( + name='status', full_name='leap.common.events.EventResponse.status', index=0, + number=1, type=14, cpp_type=8, label=2, + has_default_value=False, default_value=1, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + descriptor.FieldDescriptor( + name='result', full_name='leap.common.events.EventResponse.result', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _EVENTRESPONSE_STATUS, + ], + options=None, + is_extendable=False, + extension_ranges=[], + serialized_start=299, + serialized_end=429, +) + + +_SIGNALREQUEST.fields_by_name['event'].enum_type = _EVENT +_REGISTERREQUEST.fields_by_name['event'].enum_type = _EVENT +_EVENTRESPONSE.fields_by_name['status'].enum_type = _EVENTRESPONSE_STATUS +_EVENTRESPONSE_STATUS.containing_type = _EVENTRESPONSE + + +class SignalRequest(message.Message): + __metaclass__ = reflection.GeneratedProtocolMessageType + DESCRIPTOR = _SIGNALREQUEST + + # @@protoc_insertion_point(class_scope:leap.common.events.SignalRequest) + + +class RegisterRequest(message.Message): + __metaclass__ = reflection.GeneratedProtocolMessageType + DESCRIPTOR = _REGISTERREQUEST + + # @@protoc_insertion_point(class_scope:leap.common.events.RegisterRequest) + + +class EventResponse(message.Message): + __metaclass__ = reflection.GeneratedProtocolMessageType + DESCRIPTOR = _EVENTRESPONSE + + # @@protoc_insertion_point(class_scope:leap.common.events.EventResponse) + + +_EVENTSSERVERSERVICE = descriptor.ServiceDescriptor( + name='EventsServerService', + full_name='leap.common.events.EventsServerService', + file=DESCRIPTOR, + index=0, + options=None, + serialized_start=776, + serialized_end=961, + methods=[ + descriptor.MethodDescriptor( + name='register', + full_name='leap.common.events.EventsServerService.register', + index=0, + containing_service=None, + input_type=_REGISTERREQUEST, + output_type=_EVENTRESPONSE, + options=None, + ), + descriptor.MethodDescriptor( + name='signal', + full_name='leap.common.events.EventsServerService.signal', + index=1, + containing_service=None, + input_type=_SIGNALREQUEST, + output_type=_EVENTRESPONSE, + options=None, + ), + ]) + + +class EventsServerService(service.Service): + __metaclass__ = service_reflection.GeneratedServiceType + DESCRIPTOR = _EVENTSSERVERSERVICE + + +class EventsServerService_Stub(EventsServerService): + __metaclass__ = service_reflection.GeneratedServiceStubType + DESCRIPTOR = _EVENTSSERVERSERVICE + + +_EVENTSCOMPONENTSERVICE = descriptor.ServiceDescriptor( + name='EventsComponentService', + full_name='leap.common.events.EventsComponentService', + file=DESCRIPTOR, + index=1, + options=None, + serialized_start=963, + serialized_end=1067, + methods=[ + descriptor.MethodDescriptor( + name='signal', + full_name='leap.common.events.EventsComponentService.signal', + index=0, + containing_service=None, + input_type=_SIGNALREQUEST, + output_type=_EVENTRESPONSE, + options=None, + ), + ]) + + +class EventsComponentService(service.Service): + __metaclass__ = service_reflection.GeneratedServiceType + DESCRIPTOR = _EVENTSCOMPONENTSERVICE + + +class EventsComponentService_Stub(EventsComponentService): + __metaclass__ = service_reflection.GeneratedServiceStubType + DESCRIPTOR = _EVENTSCOMPONENTSERVICE + +# @@protoc_insertion_point(module_scope) -- cgit v1.2.3 From 5a9d09c2f90622b211d8055915a6f67a1a8acc31 Mon Sep 17 00:00:00 2001 From: drebs Date: Sun, 31 Mar 2013 21:04:40 -0300 Subject: Add a server for events mechanism. --- src/leap/common/events/daemon.py | 208 +++++++++++++++++++++++++++++++++++++++ src/leap/common/events/server.py | 150 ++++++++++++++++++++++++++++ 2 files changed, 358 insertions(+) create mode 100644 src/leap/common/events/daemon.py create mode 100644 src/leap/common/events/server.py diff --git a/src/leap/common/events/daemon.py b/src/leap/common/events/daemon.py new file mode 100644 index 0000000..40242c9 --- /dev/null +++ b/src/leap/common/events/daemon.py @@ -0,0 +1,208 @@ +# -*- coding: utf-8 -*- +# daemon.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +A singleton daemon for running RPC services using protobuf.socketrpc. +""" + + +import logging +import threading + + +from protobuf.socketrpc.server import ( + SocketRpcServer, + ThreadedTCPServer, + SocketHandler, +) + + +logger = logging.getLogger(__name__) + + +class ServiceAlreadyRunningException(Exception): + """ + Raised whenever a service is already running in this process but someone + attemped to start it in a different port. + """ + + +class EventsRpcServer(SocketRpcServer): + """ + RPC server used in server and component interfaces to receive messages. + """ + + def __init__(self, port, host='localhost'): + """ + Initialize a RPC server. + + @param port: the port in which to listen for incoming messages + @type port: int + @param host: the address to bind to + @type host: str + """ + SocketRpcServer.__init__(self, port, host) + self._server = None + + def run(self): + """ + Run the server. + """ + logger.info('Running server on port %d.' % self.port) + # parent implementation does not hold the server instance, so we do it + # here. + self._server = ThreadedTCPServer((self.host, self.port), + SocketHandler, self) + # if we chose to use a random port, fetch the port number info. + if self.port is 0: + self.port = self._server.socket.getsockname()[1] + self._server.serve_forever() + + def stop(self): + """ + Stop the server. + """ + self._server.shutdown() + + +class EventsSingletonDaemon(threading.Thread): + """ + Singleton class for for launching and terminating a daemon. + + This class is used so every part of the mechanism that needs to listen for + messages can launch its own daemon (thread) to do the job. + """ + + # Singleton instance + __instance = None + + def __new__(cls, *args, **kwargs): + """ + Return a singleton instance if it exists or create and initialize one. + """ + if len(args) is not 2: + raise TypeError("__init__() takes exactly 2 arguments (%d given)" + % len(args)) + if cls.__instance is None: + cls.__instance = object.__new__( + EventsSingletonDaemon, *args, **kwargs) + cls.__initialize(cls.__instance, args[0], args[1]) + return cls.__instance + + @staticmethod + def __initialize(self, port, service): + """ + Initialize a singleton daemon. + + This is a static method disguised as instance method that actually + does the initialization of the daemon instance. + + @param port: the port in which to listen for incoming messages + @type port: int + @param service: the service to provide in this daemon + @type service: google.protobuf.service.Service + """ + threading.Thread.__init__(self) + self._port = port + self._service = service + self._server = EventsRpcServer(self._port) + self._server.registerService(self._service) + self.daemon = True + + def __init__(self): + """ + Singleton placeholder initialization method. + + Initialization is made in __new__ so we can always return the same + instance upon object creation. + """ + pass + + @classmethod + def ensure(cls, port): + """ + Make sure the daemon instance is running. + + Each implementation of this method should call `self.ensure_service` + with the appropriate service from the `events.proto` definitions, and + return the daemon instance. + + @param port: the port in which the daemon should be listening + @type port: int + + @return: a daemon instance + @rtype: EventsSingletonDaemon + """ + raise NotImplementedError(self.ensure) + + @classmethod + def ensure_service(cls, port, service): + """ + Start the singleton instance if not already running. + + Might return ServiceAlreadyRunningException + + @param port: the port in which the daemon should be listening + @type port: int + + @return: a daemon instance + @rtype: EventsSingletonDaemon + """ + daemon = cls(port, service) + if not daemon.is_alive(): + daemon.start() + elif port and port != cls.__instance._port: + # service is running in this process but someone is trying to + # start it in another port + raise ServiceAlreadyRunningException( + "Service is already running in this process on port %d." + % self.__instance._port) + return daemon + + @classmethod + def get_instance(cls): + """ + Retrieve singleton instance of this daemon. + + @return: a daemon instance + @rtype: EventsSingletonDaemon + """ + return cls.__instance + + def run(self): + """ + Run the server. + """ + self._server.run() + + def stop(self): + """ + Stop the daemon. + """ + self._server.stop() + + def get_port(self): + """ + Retrieve the value of the port to which the service running in this + daemon is binded to. + + @return: the port to which the daemon is binded to + @rtype: int + """ + if self._port is 0: + self._port = self._server.port + return self._port diff --git a/src/leap/common/events/server.py b/src/leap/common/events/server.py new file mode 100644 index 0000000..5cc1add --- /dev/null +++ b/src/leap/common/events/server.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- +# server.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +A server for the events mechanism. + +A server can receive different kinds of requests from components: + + 1. Registration request: store component port number to be notified when + a specific signal arrives. + + 2. Signal request: redistribute the signal to registered components. +""" + + +import logging +import sets + + +from protobuf.socketrpc import RpcService +from leap.common.events import ( + events_pb2 as proto, + daemon, +) + + +logger = logging.getLogger(__name__) + + +SERVER_PORT = 8090 + +# the `registered_components` dictionary below should have the following +# format: +# +# { event_signal: [ port, ... ], ... } +# +registered_components = {} + + +def ensure_server(port=SERVER_PORT): + """ + Make sure the server is running on the given port. + + Attempt to connect to given local port. Upon success, assume that the + events server has already been started. Upon failure, start events server. + + @param port: the port in which server should be listening + @type port: int + + @return: the daemon instance or nothing + @rtype: EventsServerDaemon or None + """ + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect(('localhost', port)) + s.close() + logger.info('Server is already running on port %d.', port) + return None + except socket.error: + logger.info('Launching server on port %d.', port) + return EventsServerDaemon.ensure(port) + + +class EventsServerService(proto.EventsServerService): + """ + Service for receiving events in components. + """ + + def register(self, controller, request, done): + """ + Register a component port to be signaled when specific events come in. + + @param controller: used to mediate a single method call + @type controller: protobuf.socketrpc.controller.SocketRpcController + @param request: the request received from the component + @type request: leap.common.events.events_pb2.RegisterRequest + @param done: callback to be called when done + @type done: protobuf.socketrpc.server.Callback + """ + logger.info("Received registration request: %s" % str(request)) + # add component port to signal list + if request.event not in registered_components: + registered_components[request.event] = sets.Set() + registered_components[request.event].add(request.port) + # send response back to component + response = proto.EventResponse() + response.status = proto.EventResponse.OK + done.run(response) + + def signal(self, controller, request, done): + """ + Perform an RPC call to signal all components registered to receive a + specific signal. + + @param controller: used to mediate a single method call + @type controller: protobuf.socketrpc.controller.SocketRpcController + @param request: the request received from the component + @type request: leap.common.events.events_pb2.SignalRequest + @param done: callback to be called when done + @type done: protobuf.socketrpc.server.Callback + """ + logger.info('Received signal from component: %s', str(request)) + # send signal to all registered components + # TODO: verify signal auth + if request.event in registered_components: + for port in registered_components[request.event]: + + def callback(req, resp): + logger.info("Signal received by " + str(port)) + + service = RpcService(proto.EventsComponentService_Stub, + port, 'localhost') + service.signal(request, callback=callback) + # send response back to component + response = proto.EventResponse() + response.status = proto.EventResponse.OK + done.run(response) + + +class EventsServerDaemon(daemon.EventsSingletonDaemon): + """ + Singleton class for starting an events server daemon. + """ + + @classmethod + def ensure(cls, port): + """ + Make sure the daemon is running on the given port. + + @param port: the port in which the daemon should listen + @type port: int + + @return: a daemon instance + @rtype: EventsServerDaemon + """ + return cls.ensure_service(port, EventsServerService()) -- cgit v1.2.3 From 96c9140cb2552a70dc6620a50f169a4684aec64b Mon Sep 17 00:00:00 2001 From: drebs Date: Sun, 31 Mar 2013 21:05:17 -0300 Subject: Add support for components in events mechanism. --- src/leap/common/events/component.py | 233 ++++++++++++++++++++++++++++++++++++ src/leap/common/events/mac_auth.py | 31 +++++ 2 files changed, 264 insertions(+) create mode 100644 src/leap/common/events/component.py create mode 100644 src/leap/common/events/mac_auth.py diff --git a/src/leap/common/events/component.py b/src/leap/common/events/component.py new file mode 100644 index 0000000..4fcd6e7 --- /dev/null +++ b/src/leap/common/events/component.py @@ -0,0 +1,233 @@ +# -*- coding: utf-8 -*- +# component.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +The component end point of the events mechanism. + +Components are the communicating parties of the events mechanism. They +communicate by sending messages to a server, which in turn redistributes +messages to other components. + +When a component registers a callback for a given signal, it also tells the +server that it wants to be notified whenever signals of that type are sent by +some other component. +""" + + +import logging +import threading + + +from protobuf.socketrpc import RpcService +from leap.common.events import ( + events_pb2 as proto, + server, + daemon, + mac_auth, +) + + +logger = logging.getLogger(__name__) + + +# the `registered_callbacks` dictionary below should have the following +# format: +# +# { event_signal: [ (uid, callback), ... ], ... } +# +registered_callbacks = {} + + +class CallbackAlreadyRegistered(Exception): + """ + Raised when trying to register an already registered callback. + """ + + +def ensure_component_daemon(): + """ + Ensure the component daemon is running and listening for incoming + messages. + + @return: the daemon instance + @rtype: EventsComponentDaemon + """ + daemon = EventsComponentDaemon.ensure(0) + import time + # Because we use a random port we want to wait until a port is assigned to + # local component daemon. + while not (EventsComponentDaemon.get_instance() and + EventsComponentDaemon.get_instance().get_port()): + time.sleep(0.1) + return daemon + + +def register(signal, callback, uid=None, replace=False, reqcbk=None, + timeout=1000): + """ + Registers a callback to be called when a specific signal event is + received. + + Will timeout after timeout ms if response has not been received. The + timeout arg is only used for asynch requests. If a reqcbk callback has + been supplied the timeout arg is not used. The response value will be + returned for a synch request but nothing will be returned for an asynch + request. + + @param signal: the signal that causes the callback to be launched + @type signal: int (see the `events.proto` file) + @param callback: the callback to be called when the signal is received + @type callback: function + callback(leap.common.events.events_pb2.SignalRequest) + @param uid: a unique id for the callback + @type uid: int + @param replace: should an existent callback with same uid be replaced? + @type replace: bool + @param reqcbk: a callback to be called when a response from server is + received + @type reqcbk: function + callback(leap.common.events.events_pb2.EventResponse) + @param timeout: the timeout for synch calls + @type timeout: int + + Might raise a CallbackAlreadyRegistered exception if there's already a + callback identified by the given uid and replace is False. + + @return: the response from server for synch calls or nothing for asynch + calls + @rtype: leap.common.events.events_pb2.EventsResponse or None + """ + ensure_component_daemon() # so we can receive registered signals + # register callback locally + if signal not in registered_callbacks: + registered_callbacks[signal] = [] + cbklist = registered_callbacks[signal] + if uid and filter(lambda (x, y): x == uid, cbklist): + if not replace: + raise CallbackAlreadyRegisteredException() + else: + registered_callbacks[signal] = filter(lambda(x, y): x != uid, + cbklist) + registered_callbacks[signal].append((uid, callback)) + # register callback on server + request = proto.RegisterRequest() + request.event = signal + request.port = EventsComponentDaemon.get_instance().get_port() + request.mac_method = mac_auth.MacMethod.MAC_NONE + request.mac = "" + service = RpcService(proto.EventsServerService_Stub, + server.SERVER_PORT, 'localhost') + logger.info("Sending registration request to server: %s", str(request)) + return service.register(request, callback=reqcbk, timeout=timeout) + + +def signal(signal, content="", mac_method="", mac="", reqcbk=None, + timeout=1000): + """ + Send `signal` event to events server. + + Will timeout after timeout ms if response has not been received. The + timeout arg is only used for asynch requests. If a reqcbk callback has + been supplied the timeout arg is not used. The response value will be + returned for a synch request but nothing will be returned for an asynch + request. + + @param signal: the signal that causes the callback to be launched + @type signal: int (see the `events.proto` file) + @param content: the contents of the event signal + @type content: str + @param mac_method: the method used for auth mac + @type mac_method: str + @param mac: the content of the auth mac + @type mac: str + @param reqcbk: a callback to be called when a response from server is + received + @type reqcbk: function + callback(leap.common.events.events_pb2.EventResponse) + @param timeout: the timeout for synch calls + @type timeout: int + + @return: the response from server for synch calls or nothing for asynch + calls + @rtype: leap.common.events.events_pb2.EventsResponse or None + """ + request = proto.SignalRequest() + request.event = signal + request.content = content + request.mac_method = mac_method + request.mac = mac + service = RpcService(proto.EventsServerService_Stub, server.SERVER_PORT, + 'localhost') + logger.info("Sending signal to server: %s", str(request)) + return service.signal(request, callback=reqcbk, timeout=timeout) + + +class EventsComponentService(proto.EventsComponentService): + """ + Service for receiving signal events in components. + """ + + def __init__(self): + proto.EventsComponentService.__init__(self) + + def signal(self, controller, request, done): + """ + Receive a signal and run callbacks registered for that signal. + + This method is called whenever a signal request is received from + server. + + @param controller: used to mediate a single method call + @type controller: protobuf.socketrpc.controller.SocketRpcController + @param request: the request received from the component + @type request: leap.common.events.events_pb2.SignalRequest + @param done: callback to be called when done + @type done: protobuf.socketrpc.server.Callback + """ + logger.info('Received signal from server: %s' % str(request)) + + # run registered callbacks + # TODO: verify authentication using mac in incoming message + if request.event in registered_callbacks: + for (_, cbk) in registered_callbacks[request.event]: + # callbacks should be prepared to receive a + # events_pb2.SignalRequest. + cbk(request) + + # send response back to server + response = proto.EventResponse() + response.status = proto.EventResponse.OK + done.run(response) + + +class EventsComponentDaemon(daemon.EventsSingletonDaemon): + """ + A daemon that listens for incoming events from server. + """ + + @classmethod + def ensure(cls, port): + """ + Make sure the daemon is running on the given port. + + @param port: the port in which the daemon should listen + @type port: int + + @return: a daemon instance + @rtype: EventsComponentDaemon + """ + return cls.ensure_service(port, EventsComponentService()) diff --git a/src/leap/common/events/mac_auth.py b/src/leap/common/events/mac_auth.py new file mode 100644 index 0000000..49d48f7 --- /dev/null +++ b/src/leap/common/events/mac_auth.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# mac_auth.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +Authentication system for events. + +This is not implemented yet. +""" + + +class MacMethod(object): + """ + Representation of possible MAC authentication methods. + """ + + MAC_NONE = 'none' + MAC_HMAC = 'hmac' -- cgit v1.2.3 From 182b9fcf8c98596eafb4f04b7db2a384bb7b8c4d Mon Sep 17 00:00:00 2001 From: drebs Date: Sun, 31 Mar 2013 21:07:10 -0300 Subject: Add events module with basic API. --- src/leap/common/__init__.py | 5 +- src/leap/common/events/__init__.py | 100 +++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 src/leap/common/events/__init__.py diff --git a/src/leap/common/__init__.py b/src/leap/common/__init__.py index 3459ffb..5702ca1 100644 --- a/src/leap/common/__init__.py +++ b/src/leap/common/__init__.py @@ -3,6 +3,7 @@ import logging from leap.common import certs from leap.common import check from leap.common import files +from leap.common import events logger = logging.getLogger(__name__) @@ -13,4 +14,6 @@ except ImportError: logger.debug('PyGeoIP not found. Disabled Geo support.') HAS_GEOIP = False -__all__ = ["certs", "check", "files"] +__all__ = ["certs", "check", "files", "events"] + +__version__ = "0.2.0-dev" diff --git a/src/leap/common/events/__init__.py b/src/leap/common/events/__init__.py new file mode 100644 index 0000000..b8441bd --- /dev/null +++ b/src/leap/common/events/__init__.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +# __init__.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +An events mechanism that allows for signaling of events between components. +""" + +import logging +import socket + + +from leap.common.events import ( + events_pb2, + server, + component, + daemon, +) + + +logger = logging.getLogger(__name__) + + +def register(signal, callback, uid=None, replace=False, reqcbk=None, + timeout=1000): + """ + Register a callback to be called when the given signal is received. + + Will timeout after timeout ms if response has not been received. The + timeout arg is only used for asynch requests. If a reqcbk callback has + been supplied the timeout arg is not used. The response value will be + returned for a synch request but nothing will be returned for an asynch + request. + + @param signal: the signal that causes the callback to be launched + @type signal: int (see the `events.proto` file) + @param callback: the callback to be called when the signal is received + @type callback: function + @param uid: a unique id for the callback + @type uid: int + @param replace: should an existent callback with same uid be replaced? + @type replace: bool + @param reqcbk: a callback to be called when a response from server is + received + @type reqcbk: function + callback(leap.common.events.events_pb2.EventResponse) + @param timeout: the timeout for synch calls + @type timeout: int + + @return: the response from server for synch calls or nothing for asynch + calls + @rtype: leap.common.events.events_pb2.EventsResponse or None + """ + return component.register(signal, callback, uid, replace, reqcbk, timeout) + + +def signal(signal, content="", mac_method="", mac="", reqcbk=None, + timeout=1000): + """ + Send `signal` event to events server. + + Will timeout after timeout ms if response has not been received. The + timeout arg is only used for asynch requests. If a reqcbk callback has + been supplied the timeout arg is not used. The response value will be + returned for a synch request but nothing will be returned for an asynch + request. + + @param signal: the signal that causes the callback to be launched + @type signal: int (see the `events.proto` file) + @param content: the contents of the event signal + @type content: str + @param mac_method: the method used to auth mac + @type mac_method: str + @param mac: the content of the auth mac + @type mac: str + @param reqcbk: a callback to be called when a response from server is + received + @type reqcbk: function + callback(leap.common.events.events_pb2.EventResponse) + @param timeout: the timeout for synch calls + @type timeout: int + + @return: the response from server for synch calls or nothing for asynch + calls + @rtype: leap.common.events.events_pb2.EventsResponse or None + """ + return component.signal(signal) -- cgit v1.2.3 From 9d6f27cb2a83cb36d5201439e02dc1fb2048765b Mon Sep 17 00:00:00 2001 From: drebs Date: Sun, 31 Mar 2013 21:07:58 -0300 Subject: Add tests for events mechanism. --- setup.py | 4 +- src/leap/common/events/daemon.py | 2 +- src/leap/common/tests/__init__.py | 0 src/leap/common/tests/test_events.py | 200 +++++++++++++++++++++++++++++++++++ 4 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 src/leap/common/tests/__init__.py create mode 100644 src/leap/common/tests/test_events.py diff --git a/setup.py b/setup.py index c25693b..6732d69 100644 --- a/setup.py +++ b/setup.py @@ -46,8 +46,8 @@ setup( ), namespace_packages=["leap"], package_dir={'': 'src'}, - packages=find_packages('src'), - #test_suite='leap.common.tests', + packages=find_packages('src', exclude=['leap.common.tests']), + test_suite='leap.common.tests', install_requires=requirements, dependency_links=dependency_links, ) diff --git a/src/leap/common/events/daemon.py b/src/leap/common/events/daemon.py index 40242c9..09f3c2f 100644 --- a/src/leap/common/events/daemon.py +++ b/src/leap/common/events/daemon.py @@ -66,7 +66,7 @@ class EventsRpcServer(SocketRpcServer): # parent implementation does not hold the server instance, so we do it # here. self._server = ThreadedTCPServer((self.host, self.port), - SocketHandler, self) + SocketHandler, self) # if we chose to use a random port, fetch the port number info. if self.port is 0: self.port = self._server.socket.getsockname()[1] diff --git a/src/leap/common/tests/__init__.py b/src/leap/common/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/leap/common/tests/test_events.py b/src/leap/common/tests/test_events.py new file mode 100644 index 0000000..8c0bd36 --- /dev/null +++ b/src/leap/common/tests/test_events.py @@ -0,0 +1,200 @@ +## -*- coding: utf-8 -*- +# test_events.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import unittest +import sets +import time +from protobuf.socketrpc import RpcService +from leap.common import events +from leap.common.events import ( + server, + component, + mac_auth, +) +from leap.common.events.events_pb2 import ( + EventsServerService, + EventsServerService_Stub, + EventResponse, + SignalRequest, + RegisterRequest, + SOLEDAD_CREATING_KEYS, + CLIENT_UID, +) + + +port = 8090 + +received = False +local_callback_executed = False + + +def callback(request, reponse): + return True + + +class EventsTestCase(unittest.TestCase): + + @classmethod + def setUpClass(cls): + server.EventsServerDaemon.ensure(8090) + cls.callbacks = events.component.registered_callbacks + + @classmethod + def tearDownClass(cls): + # give some time for requests to be processed. + time.sleep(1) + + def setUp(self): + super(EventsTestCase, self).setUp() + + def tearDown(self): + #events.component.registered_callbacks = {} + server.registered_components = {} + super(EventsTestCase, self).tearDown() + + def test_service_singleton(self): + """ + Ensure that there's always just one instance of the server daemon + running. + """ + service1 = server.EventsServerDaemon.ensure(8090) + service2 = server.EventsServerDaemon.ensure(8090) + self.assertEqual(service1, service2, + "Can't get singleton class for service.") + + def test_component_register(self): + """ + Ensure components can register callbacks. + """ + self.assertTrue(1 not in self.callbacks, + 'There should should be no callback for this signal.') + events.register(1, lambda x: True) + self.assertTrue(1 in self.callbacks, + 'Could not register signal in local component.') + events.register(2, lambda x: True) + self.assertTrue(1 in self.callbacks, + 'Could not register signal in local component.') + self.assertTrue(2 in self.callbacks, + 'Could not register signal in local component.') + + def test_register_signal_replace(self): + """ + Make sure components can replace already registered callbacks. + """ + sig = 3 + cbk = lambda x: True + events.register(sig, cbk, uid=1) + self.assertRaises(Exception, events.register, sig, lambda x: True, + uid=1) + events.register(sig, lambda x: True, uid=1, replace=True) + self.assertTrue(sig in self.callbacks, 'Could not register signal.') + self.assertEqual(1, len(self.callbacks[sig]), + 'Wrong number of registered callbacks.') + + def test_signal_response_status(self): + """ + Ensure there's an appropriate response from server when signaling. + """ + sig = 4 + request = SignalRequest() + request.event = sig + request.content = 'my signal contents' + request.mac_method = mac_auth.MacMethod.MAC_NONE + request.mac = "" + service = RpcService(EventsServerService_Stub, port, 'localhost') + # test synch + response = service.signal(request, timeout=1000) + self.assertEqual(EventResponse.OK, response.status, + 'Wrong response status.') + # test asynch + + def local_callback(request, response): + global local_callback_executed + local_callback_executed = True + + events.register(sig, local_callback) + service.signal(request, callback=local_callback) + time.sleep(0.1) + self.assertTrue(local_callback_executed, + 'Local callback did not execute.') + + def test_events_server_service_register(self): + """ + Ensure the server can register components to be signaled. + """ + sig = 5 + request = RegisterRequest() + request.event = sig + request.port = 8091 + request.mac_method = mac_auth.MacMethod.MAC_NONE + request.mac = "" + service = RpcService(EventsServerService_Stub, port, 'localhost') + complist = server.registered_components + self.assertEqual({}, complist, + 'There should be no registered_ports when ' + 'server has just been created.') + response = service.register(request, timeout=1000) + self.assertTrue(sig in complist, "Signal not registered succesfully.") + self.assertTrue(8091 in complist[sig], + 'Failed registering component port.') + + def test_component_request_register(self): + """ + Ensure components can register themselves with server. + """ + sig = 6 + complist = server.registered_components + self.assertTrue(sig not in complist, + 'There should be no registered components for this ' + 'signal.') + events.register(sig, lambda x: True) + time.sleep(0.1) + port = component.EventsComponentDaemon.get_instance().get_port() + self.assertTrue(sig in complist, 'Failed registering component.') + self.assertTrue(port in complist[sig], + 'Failed registering component port.') + + def test_component_receives_signal(self): + """ + Ensure components can receive signals. + """ + sig = 7 + + def getsig(param=None): + global received + received = True + + events.register(sig, getsig) + request = SignalRequest() + request.event = sig + request.content = "" + request.mac_method = mac_auth.MacMethod.MAC_NONE + request.mac = "" + service = RpcService(EventsServerService_Stub, port, 'localhost') + response = service.signal(request, timeout=1000) + self.assertTrue(response is not None, 'Did not receive response.') + time.sleep(0.5) + self.assertTrue(received, 'Did not receive signal back.') + + def test_component_send_signal(self): + """ + Ensure components can send signals. + """ + sig = 8 + response = events.signal(sig) + self.assertTrue(response.status == response.OK, + 'Received wrong response status when signaling.') -- cgit v1.2.3 From 9b5a6961a5662edf11ecee36caa5accbbda0281d Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 1 Apr 2013 17:16:23 -0300 Subject: Add README and changes file for events mechanism. --- changes/feature_events_signals | 1 + src/leap/common/events/README | 45 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 changes/feature_events_signals create mode 100644 src/leap/common/events/README diff --git a/changes/feature_events_signals b/changes/feature_events_signals new file mode 100644 index 0000000..ae0b7b0 --- /dev/null +++ b/changes/feature_events_signals @@ -0,0 +1 @@ + o Add a mechanism for events signaling between components. diff --git a/src/leap/common/events/README b/src/leap/common/events/README new file mode 100644 index 0000000..61b320d --- /dev/null +++ b/src/leap/common/events/README @@ -0,0 +1,45 @@ +Events mechanism +================ + +The events mechanism allows for "components" to send signal events to each +other by means of a centralized server. Components can register with the +server to receive signals of certain types, and they can also send signals to +the server that will then redistribute these signals to registered components. + + +Listening daemons +----------------- + +Both components and the server listen for incoming messages by using a +listening daemon that runs in its own thread. The server daemon has to be +started explicitly, while components daemon will be started whenever a +component registers with the server to receive messages. + + +How to use it +------------- + +To start the events server: + +>>> from leap.common.events import server +>>> server.ensure_server(port=8090) + +To register a callback to be called when a given signal is raised: + +>>> from leap.common.events import ( +>>> register, +>>> events_pb2 as proto, +>>> ) +>>> +>>> def mycallback(sigreq): +>>> print str(sigreq) +>>> +>>> events.register(signal=proto.CLIENT_UID, callback=mycallback) + +To signal an event: + +>>> from leap.common.events import ( +>>> signal, +>>> events_pb2 as proto, +>>> ) +>>> signal(proto.CLIENT_UID) -- cgit v1.2.3 From 666877be3fa00c69319db935549f3b8ea3c14f6e Mon Sep 17 00:00:00 2001 From: Tomas Touceda Date: Mon, 8 Apr 2013 17:05:35 -0300 Subject: Add missing import for socket and forward signal()s paramteres --- src/leap/common/events/__init__.py | 2 +- src/leap/common/events/server.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/leap/common/events/__init__.py b/src/leap/common/events/__init__.py index b8441bd..c949080 100644 --- a/src/leap/common/events/__init__.py +++ b/src/leap/common/events/__init__.py @@ -97,4 +97,4 @@ def signal(signal, content="", mac_method="", mac="", reqcbk=None, calls @rtype: leap.common.events.events_pb2.EventsResponse or None """ - return component.signal(signal) + return component.signal(signal, content, mac_method, mac, reqcbk, timeout) diff --git a/src/leap/common/events/server.py b/src/leap/common/events/server.py index 5cc1add..e2365cb 100644 --- a/src/leap/common/events/server.py +++ b/src/leap/common/events/server.py @@ -29,6 +29,7 @@ A server can receive different kinds of requests from components: import logging import sets +import socket from protobuf.socketrpc import RpcService -- cgit v1.2.3 From e9f5294ead530c69d246e79e9576f043390a4e2a Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 9 Apr 2013 22:30:42 +0900 Subject: add pyopenssl to dependencies --- pkg/requirements.pip | 1 + setup.py | 1 + 2 files changed, 2 insertions(+) create mode 100644 pkg/requirements.pip diff --git a/pkg/requirements.pip b/pkg/requirements.pip new file mode 100644 index 0000000..f0bff40 --- /dev/null +++ b/pkg/requirements.pip @@ -0,0 +1 @@ +pyopenssl diff --git a/setup.py b/setup.py index b29c352..0156a1d 100644 --- a/setup.py +++ b/setup.py @@ -20,6 +20,7 @@ setup file for leap.common from setuptools import setup, find_packages requirements = [ + "PyOpenSSL", ] # XXX add classifiers, docs -- cgit v1.2.3 From 21539df85ed5d0e1fc2c84d66e1e37281aeee256 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 9 Apr 2013 22:45:36 +0900 Subject: add dateutil to reqs --- pkg/requirements.pip | 1 + setup.py | 1 + 2 files changed, 2 insertions(+) diff --git a/pkg/requirements.pip b/pkg/requirements.pip index f0bff40..2654349 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -1 +1,2 @@ pyopenssl +python-dateutil diff --git a/setup.py b/setup.py index 0156a1d..c0d1b7e 100644 --- a/setup.py +++ b/setup.py @@ -21,6 +21,7 @@ from setuptools import setup, find_packages requirements = [ "PyOpenSSL", + "python-dateutil", ] # XXX add classifiers, docs -- cgit v1.2.3 From 8b83009219f468b93acd7ec7a54cbd12fb3bc9fb Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 9 Apr 2013 22:48:09 +0900 Subject: changes file --- changes/bug_fix-imports | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/bug_fix-imports diff --git a/changes/bug_fix-imports b/changes/bug_fix-imports new file mode 100644 index 0000000..509f2f4 --- /dev/null +++ b/changes/bug_fix-imports @@ -0,0 +1 @@ + o Fix missing imports -- cgit v1.2.3 From 2611f4e5fd199d6c3b24d212c862ccf03fea93ff Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 9 Apr 2013 23:52:21 +0900 Subject: add BaseConfig class and its dependencies --- README.rst | 7 + pkg/requirements.pip | 4 + setup.py | 8 +- src/leap/common/config/__init__.py | 0 src/leap/common/config/baseconfig.py | 186 ++++++++++++ src/leap/common/config/pluggableconfig.py | 475 ++++++++++++++++++++++++++++++ src/leap/common/config/prefixers.py | 132 +++++++++ 7 files changed, 810 insertions(+), 2 deletions(-) create mode 100644 pkg/requirements.pip create mode 100644 src/leap/common/config/__init__.py create mode 100644 src/leap/common/config/baseconfig.py create mode 100644 src/leap/common/config/pluggableconfig.py create mode 100644 src/leap/common/config/prefixers.py diff --git a/README.rst b/README.rst index 5ea7bda..a856bad 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,10 @@ leap.common =========== A collection of shared utils used by the several python LEAP subprojects. + +* leap.common.cert +* leap.common.checks +* leap.common.config +* leap.common.events +* leap.common.files +* leap.common.testing diff --git a/pkg/requirements.pip b/pkg/requirements.pip new file mode 100644 index 0000000..ca21223 --- /dev/null +++ b/pkg/requirements.pip @@ -0,0 +1,4 @@ +jsonschema<=0.8 +pyxdg +protobuf +protobuf.socketrpc diff --git a/setup.py b/setup.py index 6732d69..45ff001 100644 --- a/setup.py +++ b/setup.py @@ -19,15 +19,19 @@ setup file for leap.common """ from setuptools import setup, find_packages - +# XXX parse pkg/requirements.pip requirements = [ + "jsonschema", + "pyxdg", 'protobuf', 'protobuf.socketrpc', ] dependency_links = [ - 'https://protobuf-socket-rpc.googlecode.com/files/protobuf.socketrpc-1.3.2-py2.6.egg#egg=protobuf.socketrpc', + # XXX this link is only for py2.6??? + # we need to get this packaged or included + "https://protobuf-socket-rpc.googlecode.com/files/protobuf.socketrpc-1.3.2-py2.6.egg#egg=protobuf.socketrpc", ] diff --git a/src/leap/common/config/__init__.py b/src/leap/common/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/leap/common/config/baseconfig.py b/src/leap/common/config/baseconfig.py new file mode 100644 index 0000000..edb9b24 --- /dev/null +++ b/src/leap/common/config/baseconfig.py @@ -0,0 +1,186 @@ +# -*- coding: utf-8 -*- +# baseconfig.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +Implements the abstract base class for configuration +""" + +import copy +import logging +import functools +import os + +from abc import ABCMeta, abstractmethod + +from leap.common.check import leap_assert +from leap.common.files import mkdir_p +from leap.common.config.pluggableconfig import PluggableConfig +from leap.common.config.prefixers import get_platform_prefixer + +logger = logging.getLogger(__name__) + + +class BaseConfig: + """ + Abstract base class for any JSON based configuration + """ + + __metaclass__ = ABCMeta + + """ + Standalone is a class wide parameter + + @param standalone: if True it will return the prefix for a + standalone application. Otherwise, it will return the system + default for configuration storage. + @type standalone: bool + """ + standalone = False + + def __init__(self): + self._data = {} + self._config_checker = None + + @abstractmethod + def _get_spec(self): + """ + Returns the spec object for the specific configuration + """ + return None + + def _safe_get_value(self, key): + """ + Tries to return a value only if the config has already been loaded + + @rtype: depends on the config structure, dict, str, array, int + @return: returns the value for the specified key in the config + """ + leap_assert(self._config_checker, "Load the config first") + return self._config_checker.config[key] + + def get_path_prefix(self): + """ + Returns the platform dependant path prefixer + + """ + return get_platform_prefixer().get_path_prefix( + standalone=self.standalone) + + def loaded(self): + """ + Returns True if the configuration has been already + loaded. False otherwise + """ + return self._config_checker is not None + + def save(self, path_list): + """ + Saves the current configuration to disk + + @param path_list: list of components that form the relative + path to configuration. The absolute path will be calculated + depending on the platform. + @type path_list: list + + @return: True if saved to disk correctly, False otherwise + """ + config_path = os.path.join(self.get_path_prefix(), *(path_list[:-1])) + mkdir_p(config_path) + + try: + self._config_checker.serialize(os.path.join(config_path, + path_list[-1])) + except Exception as e: + logger.warning("%s" % (e,)) + raise + return True + + def load(self, path="", data=None, mtime=None): + """ + Loads the configuration from disk + + @type path: str + @param path: relative path to configuration. The absolute path + will be calculated depending on the platform + + @return: True if loaded from disk correctly, False otherwise + """ + + config_path = os.path.join(self.get_path_prefix(), + path) + + self._config_checker = PluggableConfig(format="json") + self._config_checker.options = copy.deepcopy(self._get_spec()) + + try: + if data is None: + self._config_checker.load(fromfile=config_path, mtime=mtime) + else: + self._config_checker.load(data, mtime=mtime) + except Exception as e: + logger.warning("Something went wrong while loading " + + "the config from %s\n%s" % (config_path, e)) + self._config_checker = None + return False + return True + + +class LocalizedKey(object): + """ + Decorator used for keys that are localized in a configuration + """ + + def __init__(self, func, **kwargs): + self._func = func + + def __call__(self, instance, lang="en"): + """ + Tries to return the string for the specified language, otherwise + informs the problem and returns an empty string + + @param lang: language code + @type lang: str + + @return: localized value from the possible values returned by + self._func + """ + descriptions = self._func(instance) + description_lang = "" + config_lang = "en" + for key in descriptions.keys(): + if lang.startswith(key): + config_lang = key + break + + description_lang = descriptions[config_lang] + return description_lang + + def __get__(self, instance, instancetype): + """ + Implement the descriptor protocol to make decorating instance + method possible. + """ + # Return a partial function with the first argument is the instance + # of the class decorated. + return functools.partial(self.__call__, instance) + +if __name__ == "__main__": + try: + config = BaseConfig() # should throw TypeError for _get_spec + except Exception as e: + assert isinstance(e, TypeError), "Something went wrong" + print "Abstract BaseConfig class is working as expected" diff --git a/src/leap/common/config/pluggableconfig.py b/src/leap/common/config/pluggableconfig.py new file mode 100644 index 0000000..8535fa6 --- /dev/null +++ b/src/leap/common/config/pluggableconfig.py @@ -0,0 +1,475 @@ +# -*- coding: utf-8 -*- +# pluggableconfig.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +generic configuration handlers +""" +import copy +import json +import logging +import os +import time +import urlparse + +import jsonschema + +#from leap.base.util.translations import LEAPTranslatable +from leap.common.check import leap_assert + + +logger = logging.getLogger(__name__) + + +__all__ = ['PluggableConfig', + 'adaptors', + 'types', + 'UnknownOptionException', + 'MissingValueException', + 'ConfigurationProviderException', + 'TypeCastException'] + +# exceptions + + +class UnknownOptionException(Exception): + """exception raised when a non-configuration + value is present in the configuration""" + + +class MissingValueException(Exception): + """exception raised when a required value is missing""" + + +class ConfigurationProviderException(Exception): + """exception raised when a configuration provider is missing, etc""" + + +class TypeCastException(Exception): + """exception raised when a + configuration item cannot be coerced to a type""" + + +class ConfigAdaptor(object): + """ + abstract base class for config adaotors for + serialization/deserialization and custom validation + and type casting. + """ + def read(self, filename): + raise NotImplementedError("abstract base class") + + def write(self, config, filename): + with open(filename, 'w') as f: + self._write(f, config) + + def _write(self, fp, config): + raise NotImplementedError("abstract base class") + + def validate(self, config, schema): + raise NotImplementedError("abstract base class") + + +adaptors = {} + + +class JSONSchemaEncoder(json.JSONEncoder): + """ + custom default encoder that + casts python objects to json objects for + the schema validation + """ + def default(self, obj): + if obj is str: + return 'string' + if obj is unicode: + return 'string' + if obj is int: + return 'integer' + if obj is list: + return 'array' + if obj is dict: + return 'object' + if obj is bool: + return 'boolean' + + +class JSONAdaptor(ConfigAdaptor): + indent = 2 + extensions = ['json'] + + def read(self, _from): + if isinstance(_from, file): + _from_string = _from.read() + if isinstance(_from, str): + _from_string = _from + return json.loads(_from_string) + + def _write(self, fp, config): + fp.write(json.dumps(config, + indent=self.indent, + sort_keys=True)) + + def validate(self, config, schema_obj): + schema_json = JSONSchemaEncoder().encode(schema_obj) + schema = json.loads(schema_json) + jsonschema.validate(config, schema) + + +adaptors['json'] = JSONAdaptor() + +# +# Adaptors +# +# Allow to apply a predefined set of types to the +# specs, so it checks the validity of formats and cast it +# to proper python types. + +# TODO: +# - HTTPS uri + + +class DateType(object): + fmt = '%Y-%m-%d' + + def to_python(self, data): + return time.strptime(data, self.fmt) + + def get_prep_value(self, data): + return time.strftime(self.fmt, data) + + +class TranslatableType(object): + """ + a type that casts to LEAPTranslatable objects. + Used for labels we get from providers and stuff. + """ + + def to_python(self, data): + # TODO: add translatable + return data # LEAPTranslatable(data) + + # needed? we already have an extended dict... + #def get_prep_value(self, data): + #return dict(data) + + +class URIType(object): + + def to_python(self, data): + parsed = urlparse.urlparse(data) + if not parsed.scheme: + raise TypeCastException("uri %s has no schema" % data) + return parsed.geturl() + + def get_prep_value(self, data): + return data + + +class HTTPSURIType(object): + + def to_python(self, data): + parsed = urlparse.urlparse(data) + if not parsed.scheme: + raise TypeCastException("uri %s has no schema" % data) + if parsed.scheme != "https": + raise TypeCastException( + "uri %s does not has " + "https schema" % data) + return parsed.geturl() + + def get_prep_value(self, data): + return data + + +types = { + 'date': DateType(), + 'uri': URIType(), + 'https-uri': HTTPSURIType(), + 'translatable': TranslatableType(), +} + + +class PluggableConfig(object): + + options = {} + + def __init__(self, + adaptors=adaptors, + types=types, + format=None): + + self.config = {} + self.adaptors = adaptors + self.types = types + self._format = format + self.mtime = None + self.dirty = False + + @property + def option_dict(self): + if hasattr(self, 'options') and isinstance(self.options, dict): + return self.options.get('properties', None) + + def items(self): + """ + act like an iterator + """ + if isinstance(self.option_dict, dict): + return self.option_dict.items() + return self.options + + def validate(self, config, format=None): + """ + validate config + """ + schema = self.options + if format is None: + format = self._format + + if format: + adaptor = self.get_adaptor(self._format) + adaptor.validate(config, schema) + else: + # we really should make format mandatory... + logger.error('no format passed to validate') + + # first round of validation is ok. + # now we proceed to cast types if any specified. + self.to_python(config) + + def to_python(self, config): + """ + cast types following first type and then format indications. + """ + unseen_options = [i for i in config if i not in self.option_dict] + if unseen_options: + raise UnknownOptionException( + "Unknown options: %s" % ', '.join(unseen_options)) + + for key, value in config.items(): + _type = self.option_dict[key].get('type') + if _type is None and 'default' in self.option_dict[key]: + _type = type(self.option_dict[key]['default']) + if _type is not None: + tocast = True + if not callable(_type) and isinstance(value, _type): + tocast = False + if tocast: + try: + config[key] = _type(value) + except BaseException, e: + raise TypeCastException( + "Could not coerce %s, %s, " + "to type %s: %s" % (key, value, _type.__name__, e)) + _format = self.option_dict[key].get('format', None) + _ftype = self.types.get(_format, None) + if _ftype: + try: + config[key] = _ftype.to_python(value) + except BaseException, e: + raise TypeCastException( + "Could not coerce %s, %s, " + "to format %s: %s" % (key, value, + _ftype.__class__.__name__, + e)) + + return config + + def prep_value(self, config): + """ + the inverse of to_python method, + called just before serialization + """ + for key, value in config.items(): + _format = self.option_dict[key].get('format', None) + _ftype = self.types.get(_format, None) + if _ftype and hasattr(_ftype, 'get_prep_value'): + try: + config[key] = _ftype.get_prep_value(value) + except BaseException, e: + raise TypeCastException( + "Could not serialize %s, %s, " + "by format %s: %s" % (key, value, + _ftype.__class__.__name__, + e)) + else: + config[key] = value + return config + + # methods for adding configuration + + def get_default_values(self): + """ + return a config options from configuration defaults + """ + defaults = {} + for key, value in self.items(): + if 'default' in value: + defaults[key] = value['default'] + return copy.deepcopy(defaults) + + def get_adaptor(self, format): + """ + get specified format adaptor or + guess for a given filename + """ + adaptor = self.adaptors.get(format, None) + if adaptor: + return adaptor + + # not registered in adaptors dict, let's try all + for adaptor in self.adaptors.values(): + if format in adaptor.extensions: + return adaptor + + def filename2format(self, filename): + extension = os.path.splitext(filename)[-1] + return extension.lstrip('.') or None + + def serialize(self, filename, format=None, full=False): + if not format: + format = self._format + if not format: + format = self.filename2format(filename) + if not format: + raise Exception('Please specify a format') + # TODO: more specific exception type + + adaptor = self.get_adaptor(format) + if not adaptor: + raise Exception("Adaptor not found for format: %s" % format) + + config = copy.deepcopy(self.config) + serializable = self.prep_value(config) + adaptor.write(serializable, filename) + + if self.mtime: + self.touch_mtime(filename) + + def touch_mtime(self, filename): + mtime = self.mtime + os.utime(filename, (mtime, mtime)) + + def deserialize(self, string=None, fromfile=None, format=None): + """ + load configuration from a file or string + """ + + def _try_deserialize(): + if fromfile: + with open(fromfile, 'r') as f: + content = adaptor.read(f) + elif string: + content = adaptor.read(string) + return content + + # XXX cleanup this! + + if fromfile: + leap_assert(os.path.exists(fromfile)) + if not format: + format = self.filename2format(fromfile) + + if not format: + format = self._format + if format: + adaptor = self.get_adaptor(format) + else: + adaptor = None + + if adaptor: + content = _try_deserialize() + return content + + # no adaptor, let's try rest of adaptors + + adaptors = self.adaptors[:] + + if format: + adaptors.sort( + key=lambda x: int( + format in x.extensions), + reverse=True) + + for adaptor in adaptors: + content = _try_deserialize() + return content + + def set_dirty(self): + self.dirty = True + + def is_dirty(self): + return self.dirty + + def load(self, *args, **kwargs): + """ + load from string or file + if no string of fromfile option is given, + it will attempt to load from defaults + defined in the schema. + """ + string = args[0] if args else None + fromfile = kwargs.get("fromfile", None) + mtime = kwargs.pop("mtime", None) + self.mtime = mtime + content = None + + # start with defaults, so we can + # have partial values applied. + content = self.get_default_values() + if string and isinstance(string, str): + content = self.deserialize(string) + + if not string and fromfile is not None: + #import ipdb;ipdb.set_trace() + content = self.deserialize(fromfile=fromfile) + + if not content: + logger.error('no content could be loaded') + # XXX raise! + return + + # lazy evaluation until first level of nesting + # to allow lambdas with context-dependant info + # like os.path.expanduser + for k, v in content.iteritems(): + if callable(v): + content[k] = v() + + self.validate(content) + self.config = content + return True + + +def testmain(): # pragma: no cover + + from tests import test_validation as t + import pprint + + config = PluggableConfig(_format="json") + properties = copy.deepcopy(t.sample_spec) + + config.options = properties + config.load(fromfile='data.json') + + print 'config' + pprint.pprint(config.config) + + config.serialize('/tmp/testserial.json') + +if __name__ == "__main__": + testmain() diff --git a/src/leap/common/config/prefixers.py b/src/leap/common/config/prefixers.py new file mode 100644 index 0000000..27274bd --- /dev/null +++ b/src/leap/common/config/prefixers.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- +# prefixers.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +Platform dependant configuration path prefixers +""" +import os +import platform + +from abc import ABCMeta, abstractmethod +from xdg import BaseDirectory + +from leap.common.check import leap_assert + + +class Prefixer: + """ + Abstract prefixer class + """ + + __metaclass__ = ABCMeta + + @abstractmethod + def get_path_prefix(self, standalone=False): + """ + Returns the platform dependant path prefixer + + @param standalone: if True it will return the prefix for a + standalone application. Otherwise, it will return the system + default for configuration storage. + @type standalone: bool + """ + return "" + + +def get_platform_prefixer(): + prefixer = globals()[platform.system() + "Prefixer"] + leap_assert(prefixer, "Unimplemented platform prefixer: %s" % + (platform.system(),)) + return prefixer() + + +class LinuxPrefixer(Prefixer): + """ + Config prefixer for the Linux platform + """ + + def get_path_prefix(self, standalone=False): + """ + Returns the platform dependant path prefixer. + This method expects an env variable named LEAP_CLIENT_PATH if + standalone is used. + + @param standalone: if True it will return the prefix for a + standalone application. Otherwise, it will return the system + default for configuration storage. + @type standalone: bool + """ + config_dir = BaseDirectory.xdg_config_home + if not standalone: + return config_dir + return os.path.join(os.getcwd(), "config") + + +class DarwinPrefixer(Prefixer): + """ + Config prefixer for the Darwin platform + """ + + def get_path_prefix(self, standalone=False): + """ + Returns the platform dependant path prefixer. + This method expects an env variable named LEAP_CLIENT_PATH if + standalone is used. + + @param standalone: if True it will return the prefix for a + standalone application. Otherwise, it will return the system + default for configuration storage. + @type standalone: bool + """ + config_dir = BaseDirectory.xdg_config_home + if not standalone: + return config_dir + return os.getenv(os.getcwd(), "config") + + +class WindowsPrefixer(Prefixer): + """ + Config prefixer for the Windows platform + """ + + def get_path_prefix(self, standalone=False): + """ + Returns the platform dependant path prefixer. + This method expects an env variable named LEAP_CLIENT_PATH if + standalone is used. + + @param standalone: if True it will return the prefix for a + standalone application. Otherwise, it will return the system + default for configuration storage. + @type standalone: bool + """ + config_dir = BaseDirectory.xdg_config_home + + if not standalone: + return config_dir + return os.path.join(os.getcwd(), "config") + +if __name__ == "__main__": + try: + abs_prefixer = Prefixer() + except Exception as e: + assert isinstance(e, TypeError), "Something went wrong" + print "Abstract Prefixer class is working as expected" + + linux_prefixer = LinuxPrefixer() + print linux_prefixer.get_path_prefix(standalone=True) + print linux_prefixer.get_path_prefix() -- cgit v1.2.3 From 8c9c8fb20b87a1a66f373ccc9e2e60ddd8266cde Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 10 Apr 2013 00:07:07 +0900 Subject: add generic tarball to download link for protobuf.rpc --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 45ff001..31fc8d3 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,8 @@ requirements = [ dependency_links = [ # XXX this link is only for py2.6??? # we need to get this packaged or included - "https://protobuf-socket-rpc.googlecode.com/files/protobuf.socketrpc-1.3.2-py2.6.egg#egg=protobuf.socketrpc", + #"https://protobuf-socket-rpc.googlecode.com/files/protobuf.socketrpc-1.3.2-py2.6.egg#egg=protobuf.socketrpc", + "https://code.google.com/p/protobuf-socket-rpc/downloads/detail?name=protobuf.socketrpc-1.3.2.tar.gz#egg=protobuf.socketrpc", ] -- cgit v1.2.3 From b16426c7d7e77dac89cf86730df4e3ccea96fac4 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 11 Apr 2013 19:36:28 +0900 Subject: fix deprecation warnings; add debug info --- changes/bug_fix-deprecation-warning | 1 + src/leap/common/events/component.py | 5 ++++- src/leap/common/events/daemon.py | 2 +- src/leap/common/events/server.py | 8 +++----- 4 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 changes/bug_fix-deprecation-warning diff --git a/changes/bug_fix-deprecation-warning b/changes/bug_fix-deprecation-warning new file mode 100644 index 0000000..ac58117 --- /dev/null +++ b/changes/bug_fix-deprecation-warning @@ -0,0 +1 @@ + o Fix deprecation warnings diff --git a/src/leap/common/events/component.py b/src/leap/common/events/component.py index 4fcd6e7..bec1898 100644 --- a/src/leap/common/events/component.py +++ b/src/leap/common/events/component.py @@ -66,10 +66,13 @@ def ensure_component_daemon(): @return: the daemon instance @rtype: EventsComponentDaemon """ - daemon = EventsComponentDaemon.ensure(0) import time + daemon = EventsComponentDaemon.ensure(0) + logger.debug('ensure component daemon') + # Because we use a random port we want to wait until a port is assigned to # local component daemon. + while not (EventsComponentDaemon.get_instance() and EventsComponentDaemon.get_instance().get_port()): time.sleep(0.1) diff --git a/src/leap/common/events/daemon.py b/src/leap/common/events/daemon.py index 09f3c2f..d2c7b9b 100644 --- a/src/leap/common/events/daemon.py +++ b/src/leap/common/events/daemon.py @@ -99,7 +99,7 @@ class EventsSingletonDaemon(threading.Thread): % len(args)) if cls.__instance is None: cls.__instance = object.__new__( - EventsSingletonDaemon, *args, **kwargs) + EventsSingletonDaemon) cls.__initialize(cls.__instance, args[0], args[1]) return cls.__instance diff --git a/src/leap/common/events/server.py b/src/leap/common/events/server.py index e2365cb..16c6513 100644 --- a/src/leap/common/events/server.py +++ b/src/leap/common/events/server.py @@ -14,7 +14,6 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . - """ A server for the events mechanism. @@ -25,10 +24,7 @@ A server can receive different kinds of requests from components: 2. Signal request: redistribute the signal to registered components. """ - - import logging -import sets import socket @@ -95,9 +91,11 @@ class EventsServerService(proto.EventsServerService): logger.info("Received registration request: %s" % str(request)) # add component port to signal list if request.event not in registered_components: - registered_components[request.event] = sets.Set() + registered_components[request.event] = set([]) registered_components[request.event].add(request.port) # send response back to component + + logger.debug('sending response back') response = proto.EventResponse() response.status = proto.EventResponse.OK done.run(response) -- cgit v1.2.3 From 1693669bacac1febcc81ad782028e216804d6f35 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 11 Apr 2013 19:48:52 +0900 Subject: add debug info about server port --- src/leap/common/events/component.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/leap/common/events/component.py b/src/leap/common/events/component.py index bec1898..0cf0e38 100644 --- a/src/leap/common/events/component.py +++ b/src/leap/common/events/component.py @@ -134,7 +134,9 @@ def register(signal, callback, uid=None, replace=False, reqcbk=None, request.mac = "" service = RpcService(proto.EventsServerService_Stub, server.SERVER_PORT, 'localhost') - logger.info("Sending registration request to server: %s", str(request)) + logger.info("Sending registration request to server on port %s: %s", + server.SERVER_PORT, + str(request)) return service.register(request, callback=reqcbk, timeout=timeout) -- cgit v1.2.3 From a982a0de2b290beecf907a2210b43faef1bb70c0 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 10 Apr 2013 20:03:15 +0900 Subject: add RAISE_WINDOW event --- README.rst | 8 + changes/feature_raise-window-event | 1 + pkg/requirements.pip | 1 + src/leap/common/events/README | 45 --- src/leap/common/events/README.rst | 53 ++++ src/leap/common/events/events.proto | 1 + src/leap/common/events/events_pb2.py | 550 +++++++++++++++-------------------- 7 files changed, 305 insertions(+), 354 deletions(-) create mode 100644 changes/feature_raise-window-event delete mode 100644 src/leap/common/events/README create mode 100644 src/leap/common/events/README.rst diff --git a/README.rst b/README.rst index a856bad..f960b1f 100644 --- a/README.rst +++ b/README.rst @@ -8,3 +8,11 @@ A collection of shared utils used by the several python LEAP subprojects. * leap.common.events * leap.common.files * leap.common.testing + +Library dependencies +-------------------- +* ``protobuf-compiler`` + +Python dependencies +------------------- +* See ``pkg/requirements.pip`` diff --git a/changes/feature_raise-window-event b/changes/feature_raise-window-event new file mode 100644 index 0000000..382ff3f --- /dev/null +++ b/changes/feature_raise-window-event @@ -0,0 +1 @@ + o Add RAISE_WINDOW event diff --git a/pkg/requirements.pip b/pkg/requirements.pip index 85e5aef..99d8533 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -4,3 +4,4 @@ protobuf protobuf.socketrpc pyopenssl python-dateutil +autopep8 diff --git a/src/leap/common/events/README b/src/leap/common/events/README deleted file mode 100644 index 61b320d..0000000 --- a/src/leap/common/events/README +++ /dev/null @@ -1,45 +0,0 @@ -Events mechanism -================ - -The events mechanism allows for "components" to send signal events to each -other by means of a centralized server. Components can register with the -server to receive signals of certain types, and they can also send signals to -the server that will then redistribute these signals to registered components. - - -Listening daemons ------------------ - -Both components and the server listen for incoming messages by using a -listening daemon that runs in its own thread. The server daemon has to be -started explicitly, while components daemon will be started whenever a -component registers with the server to receive messages. - - -How to use it -------------- - -To start the events server: - ->>> from leap.common.events import server ->>> server.ensure_server(port=8090) - -To register a callback to be called when a given signal is raised: - ->>> from leap.common.events import ( ->>> register, ->>> events_pb2 as proto, ->>> ) ->>> ->>> def mycallback(sigreq): ->>> print str(sigreq) ->>> ->>> events.register(signal=proto.CLIENT_UID, callback=mycallback) - -To signal an event: - ->>> from leap.common.events import ( ->>> signal, ->>> events_pb2 as proto, ->>> ) ->>> signal(proto.CLIENT_UID) diff --git a/src/leap/common/events/README.rst b/src/leap/common/events/README.rst new file mode 100644 index 0000000..813be8b --- /dev/null +++ b/src/leap/common/events/README.rst @@ -0,0 +1,53 @@ +Events mechanism +================ + +The events mechanism allows for "components" to send signal events to each +other by means of a centralized server. Components can register with the +server to receive signals of certain types, and they can also send signals to +the server that will then redistribute these signals to registered components. + + +Listening daemons +----------------- + +Both components and the server listen for incoming messages by using a +listening daemon that runs in its own thread. The server daemon has to be +started explicitly, while components daemon will be started whenever a +component registers with the server to receive messages. + + +How to use it +------------- + +To start the events server: + +>>> from leap.common.events import server +>>> server.ensure_server(port=8090) + +To register a callback to be called when a given signal is raised: + +>>> from leap.common.events import ( +>>> register, +>>> events_pb2 as proto, +>>> ) +>>> +>>> def mycallback(sigreq): +>>> print str(sigreq) +>>> +>>> events.register(signal=proto.CLIENT_UID, callback=mycallback) + +To signal an event: + +>>> from leap.common.events import ( +>>> signal, +>>> events_pb2 as proto, +>>> ) +>>> signal(proto.CLIENT_UID) + +Adding events +------------- + +* Add the new event under enum ``Event`` in ``events.proto`` +* Compile the new protocolbuffers file:: + + make diff --git a/src/leap/common/events/events.proto b/src/leap/common/events/events.proto index 29388b8..eb6b98f 100644 --- a/src/leap/common/events/events.proto +++ b/src/leap/common/events/events.proto @@ -29,6 +29,7 @@ enum Event { SOLEDAD_DONE_DATA_SYNC = 10; UPDATER_NEW_UPDATES = 11; UPDATER_DONE_UPDATING = 12; + RAISE_WINDOW = 13; } message SignalRequest { diff --git a/src/leap/common/events/events_pb2.py b/src/leap/common/events/events_pb2.py index 1d278cc..869378a 100644 --- a/src/leap/common/events/events_pb2.py +++ b/src/leap/common/events/events_pb2.py @@ -3,76 +3,79 @@ from google.protobuf import descriptor from google.protobuf import message from google.protobuf import reflection -from google.protobuf import service -from google.protobuf import service_reflection from google.protobuf import descriptor_pb2 # @@protoc_insertion_point(imports) + DESCRIPTOR = descriptor.FileDescriptor( - name='events.proto', - package='leap.common.events', - serialized_pb='\n\x0c\x65vents.proto\x12\x12leap.common.events\"\x97\x01\n\rSignalRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0f\n\x07\x63ontent\x18\x02 \x02(\t\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\x12\x12\n\nenc_method\x18\x05 \x01(\t\x12\x16\n\x0e\x65rror_occurred\x18\x06 \x01(\x08\"j\n\x0fRegisterRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0c\n\x04port\x18\x02 \x02(\x05\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\"\x82\x01\n\rEventResponse\x12\x38\n\x06status\x18\x01 \x02(\x0e\x32(.leap.common.events.EventResponse.Status\x12\x0e\n\x06result\x18\x02 \x01(\t\"\'\n\x06Status\x12\x06\n\x02OK\x10\x01\x12\n\n\x06UNAUTH\x10\x02\x12\t\n\x05\x45RROR\x10\x03*\xd5\x02\n\x05\x45vent\x12\x15\n\x11\x43LIENT_SESSION_ID\x10\x01\x12\x0e\n\nCLIENT_UID\x10\x02\x12\x19\n\x15SOLEDAD_CREATING_KEYS\x10\x03\x12\x1e\n\x1aSOLEDAD_DONE_CREATING_KEYS\x10\x04\x12\x1a\n\x16SOLEDAD_UPLOADING_KEYS\x10\x05\x12\x1f\n\x1bSOLEDAD_DONE_UPLOADING_KEYS\x10\x06\x12\x1c\n\x18SOLEDAD_DOWNLOADING_KEYS\x10\x07\x12!\n\x1dSOLEDAD_DONE_DOWNLOADING_KEYS\x10\x08\x12\x1c\n\x18SOLEDAD_NEW_DATA_TO_SYNC\x10\t\x12\x1a\n\x16SOLEDAD_DONE_DATA_SYNC\x10\n\x12\x17\n\x13UPDATER_NEW_UPDATES\x10\x0b\x12\x19\n\x15UPDATER_DONE_UPDATING\x10\x0c\x32\xb9\x01\n\x13\x45ventsServerService\x12R\n\x08register\x12#.leap.common.events.RegisterRequest\x1a!.leap.common.events.EventResponse\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponse2h\n\x16\x45ventsComponentService\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponse') + name='events.proto', + package='leap.common.events', + serialized_pb='\n\x0c\x65vents.proto\x12\x12leap.common.events\"\x97\x01\n\rSignalRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0f\n\x07\x63ontent\x18\x02 \x02(\t\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\x12\x12\n\nenc_method\x18\x05 \x01(\t\x12\x16\n\x0e\x65rror_occurred\x18\x06 \x01(\x08\"j\n\x0fRegisterRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0c\n\x04port\x18\x02 \x02(\x05\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\"\x82\x01\n\rEventResponse\x12\x38\n\x06status\x18\x01 \x02(\x0e\x32(.leap.common.events.EventResponse.Status\x12\x0e\n\x06result\x18\x02 \x01(\t\"\'\n\x06Status\x12\x06\n\x02OK\x10\x01\x12\n\n\x06UNAUTH\x10\x02\x12\t\n\x05\x45RROR\x10\x03*\xe7\x02\n\x05\x45vent\x12\x15\n\x11\x43LIENT_SESSION_ID\x10\x01\x12\x0e\n\nCLIENT_UID\x10\x02\x12\x19\n\x15SOLEDAD_CREATING_KEYS\x10\x03\x12\x1e\n\x1aSOLEDAD_DONE_CREATING_KEYS\x10\x04\x12\x1a\n\x16SOLEDAD_UPLOADING_KEYS\x10\x05\x12\x1f\n\x1bSOLEDAD_DONE_UPLOADING_KEYS\x10\x06\x12\x1c\n\x18SOLEDAD_DOWNLOADING_KEYS\x10\x07\x12!\n\x1dSOLEDAD_DONE_DOWNLOADING_KEYS\x10\x08\x12\x1c\n\x18SOLEDAD_NEW_DATA_TO_SYNC\x10\t\x12\x1a\n\x16SOLEDAD_DONE_DATA_SYNC\x10\n\x12\x17\n\x13UPDATER_NEW_UPDATES\x10\x0b\x12\x19\n\x15UPDATER_DONE_UPDATING\x10\x0c\x12\x10\n\x0cRAISE_WINDOW\x10\r2\xb9\x01\n\x13\x45ventsServerService\x12R\n\x08register\x12#.leap.common.events.RegisterRequest\x1a!.leap.common.events.EventResponse\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponse2h\n\x16\x45ventsComponentService\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponse') _EVENT = descriptor.EnumDescriptor( - name='Event', - full_name='leap.common.events.Event', - filename=None, - file=DESCRIPTOR, - values=[ - descriptor.EnumValueDescriptor( - name='CLIENT_SESSION_ID', index=0, number=1, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='CLIENT_UID', index=1, number=2, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='SOLEDAD_CREATING_KEYS', index=2, number=3, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='SOLEDAD_DONE_CREATING_KEYS', index=3, number=4, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='SOLEDAD_UPLOADING_KEYS', index=4, number=5, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='SOLEDAD_DONE_UPLOADING_KEYS', index=5, number=6, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='SOLEDAD_DOWNLOADING_KEYS', index=6, number=7, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='SOLEDAD_DONE_DOWNLOADING_KEYS', index=7, number=8, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='SOLEDAD_NEW_DATA_TO_SYNC', index=8, number=9, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='SOLEDAD_DONE_DATA_SYNC', index=9, number=10, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='UPDATER_NEW_UPDATES', index=10, number=11, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='UPDATER_DONE_UPDATING', index=11, number=12, - options=None, - type=None), - ], - containing_type=None, - options=None, - serialized_start=432, - serialized_end=773, + name='Event', + full_name='leap.common.events.Event', + filename=None, + file=DESCRIPTOR, + values=[ + descriptor.EnumValueDescriptor( + name='CLIENT_SESSION_ID', index=0, number=1, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='CLIENT_UID', index=1, number=2, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_CREATING_KEYS', index=2, number=3, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_DONE_CREATING_KEYS', index=3, number=4, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_UPLOADING_KEYS', index=4, number=5, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_DONE_UPLOADING_KEYS', index=5, number=6, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_DOWNLOADING_KEYS', index=6, number=7, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_DONE_DOWNLOADING_KEYS', index=7, number=8, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_NEW_DATA_TO_SYNC', index=8, number=9, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_DONE_DATA_SYNC', index=9, number=10, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='UPDATER_NEW_UPDATES', index=10, number=11, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='UPDATER_DONE_UPDATING', index=11, number=12, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='RAISE_WINDOW', index=12, number=13, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=432, + serialized_end=791, ) @@ -88,277 +91,206 @@ SOLEDAD_NEW_DATA_TO_SYNC = 9 SOLEDAD_DONE_DATA_SYNC = 10 UPDATER_NEW_UPDATES = 11 UPDATER_DONE_UPDATING = 12 +RAISE_WINDOW = 13 _EVENTRESPONSE_STATUS = descriptor.EnumDescriptor( - name='Status', - full_name='leap.common.events.EventResponse.Status', - filename=None, - file=DESCRIPTOR, - values=[ - descriptor.EnumValueDescriptor( - name='OK', index=0, number=1, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='UNAUTH', index=1, number=2, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='ERROR', index=2, number=3, - options=None, - type=None), - ], - containing_type=None, - options=None, - serialized_start=390, - serialized_end=429, + name='Status', + full_name='leap.common.events.EventResponse.Status', + filename=None, + file=DESCRIPTOR, + values=[ + descriptor.EnumValueDescriptor( + name='OK', index=0, number=1, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='UNAUTH', index=1, number=2, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='ERROR', index=2, number=3, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=390, + serialized_end=429, ) _SIGNALREQUEST = descriptor.Descriptor( - name='SignalRequest', - full_name='leap.common.events.SignalRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - descriptor.FieldDescriptor( - name='event', full_name='leap.common.events.SignalRequest.event', index=0, - number=1, type=14, cpp_type=8, label=2, - has_default_value=False, default_value=1, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - descriptor.FieldDescriptor( - name='content', full_name='leap.common.events.SignalRequest.content', index=1, - number=2, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - descriptor.FieldDescriptor( - name='mac_method', full_name='leap.common.events.SignalRequest.mac_method', index=2, - number=3, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - descriptor.FieldDescriptor( - name='mac', full_name='leap.common.events.SignalRequest.mac', index=3, - number=4, type=12, cpp_type=9, label=2, - has_default_value=False, default_value="", - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - descriptor.FieldDescriptor( - name='enc_method', full_name='leap.common.events.SignalRequest.enc_method', index=4, - number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=unicode("", "utf-8"), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - descriptor.FieldDescriptor( - name='error_occurred', full_name='leap.common.events.SignalRequest.error_occurred', index=5, - number=6, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - extension_ranges=[], - serialized_start=37, - serialized_end=188, + name='SignalRequest', + full_name='leap.common.events.SignalRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + descriptor.FieldDescriptor( + name='event', full_name='leap.common.events.SignalRequest.event', index=0, + number=1, type=14, cpp_type=8, label=2, + has_default_value=False, default_value=1, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + descriptor.FieldDescriptor( + name='content', full_name='leap.common.events.SignalRequest.content', index=1, + number=2, type=9, cpp_type=9, label=2, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + descriptor.FieldDescriptor( + name='mac_method', full_name='leap.common.events.SignalRequest.mac_method', index=2, + number=3, type=9, cpp_type=9, label=2, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + descriptor.FieldDescriptor( + name='mac', full_name='leap.common.events.SignalRequest.mac', index=3, + number=4, type=12, cpp_type=9, label=2, + has_default_value=False, default_value="", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + descriptor.FieldDescriptor( + name='enc_method', full_name='leap.common.events.SignalRequest.enc_method', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + descriptor.FieldDescriptor( + name='error_occurred', full_name='leap.common.events.SignalRequest.error_occurred', index=5, + number=6, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + extension_ranges=[], + serialized_start=37, + serialized_end=188, ) _REGISTERREQUEST = descriptor.Descriptor( - name='RegisterRequest', - full_name='leap.common.events.RegisterRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - descriptor.FieldDescriptor( - name='event', full_name='leap.common.events.RegisterRequest.event', index=0, - number=1, type=14, cpp_type=8, label=2, - has_default_value=False, default_value=1, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - descriptor.FieldDescriptor( - name='port', full_name='leap.common.events.RegisterRequest.port', index=1, - number=2, type=5, cpp_type=1, label=2, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - descriptor.FieldDescriptor( - name='mac_method', full_name='leap.common.events.RegisterRequest.mac_method', index=2, - number=3, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - descriptor.FieldDescriptor( - name='mac', full_name='leap.common.events.RegisterRequest.mac', index=3, - number=4, type=12, cpp_type=9, label=2, - has_default_value=False, default_value="", - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - extension_ranges=[], - serialized_start=190, - serialized_end=296, + name='RegisterRequest', + full_name='leap.common.events.RegisterRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + descriptor.FieldDescriptor( + name='event', full_name='leap.common.events.RegisterRequest.event', index=0, + number=1, type=14, cpp_type=8, label=2, + has_default_value=False, default_value=1, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + descriptor.FieldDescriptor( + name='port', full_name='leap.common.events.RegisterRequest.port', index=1, + number=2, type=5, cpp_type=1, label=2, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + descriptor.FieldDescriptor( + name='mac_method', full_name='leap.common.events.RegisterRequest.mac_method', index=2, + number=3, type=9, cpp_type=9, label=2, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + descriptor.FieldDescriptor( + name='mac', full_name='leap.common.events.RegisterRequest.mac', index=3, + number=4, type=12, cpp_type=9, label=2, + has_default_value=False, default_value="", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + extension_ranges=[], + serialized_start=190, + serialized_end=296, ) _EVENTRESPONSE = descriptor.Descriptor( - name='EventResponse', - full_name='leap.common.events.EventResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - descriptor.FieldDescriptor( - name='status', full_name='leap.common.events.EventResponse.status', index=0, - number=1, type=14, cpp_type=8, label=2, - has_default_value=False, default_value=1, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - descriptor.FieldDescriptor( - name='result', full_name='leap.common.events.EventResponse.result', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=unicode("", "utf-8"), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _EVENTRESPONSE_STATUS, - ], - options=None, - is_extendable=False, - extension_ranges=[], - serialized_start=299, - serialized_end=429, + name='EventResponse', + full_name='leap.common.events.EventResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + descriptor.FieldDescriptor( + name='status', full_name='leap.common.events.EventResponse.status', index=0, + number=1, type=14, cpp_type=8, label=2, + has_default_value=False, default_value=1, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + descriptor.FieldDescriptor( + name='result', full_name='leap.common.events.EventResponse.result', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _EVENTRESPONSE_STATUS, + ], + options=None, + is_extendable=False, + extension_ranges=[], + serialized_start=299, + serialized_end=429, ) - _SIGNALREQUEST.fields_by_name['event'].enum_type = _EVENT _REGISTERREQUEST.fields_by_name['event'].enum_type = _EVENT _EVENTRESPONSE.fields_by_name['status'].enum_type = _EVENTRESPONSE_STATUS -_EVENTRESPONSE_STATUS.containing_type = _EVENTRESPONSE - +_EVENTRESPONSE_STATUS.containing_type = _EVENTRESPONSE; +DESCRIPTOR.message_types_by_name['SignalRequest'] = _SIGNALREQUEST +DESCRIPTOR.message_types_by_name['RegisterRequest'] = _REGISTERREQUEST +DESCRIPTOR.message_types_by_name['EventResponse'] = _EVENTRESPONSE class SignalRequest(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _SIGNALREQUEST - - # @@protoc_insertion_point(class_scope:leap.common.events.SignalRequest) - + __metaclass__ = reflection.GeneratedProtocolMessageType + DESCRIPTOR = _SIGNALREQUEST + + # @@protoc_insertion_point(class_scope:leap.common.events.SignalRequest) class RegisterRequest(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _REGISTERREQUEST - - # @@protoc_insertion_point(class_scope:leap.common.events.RegisterRequest) - + __metaclass__ = reflection.GeneratedProtocolMessageType + DESCRIPTOR = _REGISTERREQUEST + + # @@protoc_insertion_point(class_scope:leap.common.events.RegisterRequest) class EventResponse(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _EVENTRESPONSE - - # @@protoc_insertion_point(class_scope:leap.common.events.EventResponse) - - -_EVENTSSERVERSERVICE = descriptor.ServiceDescriptor( - name='EventsServerService', - full_name='leap.common.events.EventsServerService', - file=DESCRIPTOR, - index=0, - options=None, - serialized_start=776, - serialized_end=961, - methods=[ - descriptor.MethodDescriptor( - name='register', - full_name='leap.common.events.EventsServerService.register', - index=0, - containing_service=None, - input_type=_REGISTERREQUEST, - output_type=_EVENTRESPONSE, - options=None, - ), - descriptor.MethodDescriptor( - name='signal', - full_name='leap.common.events.EventsServerService.signal', - index=1, - containing_service=None, - input_type=_SIGNALREQUEST, - output_type=_EVENTRESPONSE, - options=None, - ), - ]) - - -class EventsServerService(service.Service): - __metaclass__ = service_reflection.GeneratedServiceType - DESCRIPTOR = _EVENTSSERVERSERVICE - - -class EventsServerService_Stub(EventsServerService): - __metaclass__ = service_reflection.GeneratedServiceStubType - DESCRIPTOR = _EVENTSSERVERSERVICE - - -_EVENTSCOMPONENTSERVICE = descriptor.ServiceDescriptor( - name='EventsComponentService', - full_name='leap.common.events.EventsComponentService', - file=DESCRIPTOR, - index=1, - options=None, - serialized_start=963, - serialized_end=1067, - methods=[ - descriptor.MethodDescriptor( - name='signal', - full_name='leap.common.events.EventsComponentService.signal', - index=0, - containing_service=None, - input_type=_SIGNALREQUEST, - output_type=_EVENTRESPONSE, - options=None, - ), - ]) - - -class EventsComponentService(service.Service): - __metaclass__ = service_reflection.GeneratedServiceType - DESCRIPTOR = _EVENTSCOMPONENTSERVICE - - -class EventsComponentService_Stub(EventsComponentService): - __metaclass__ = service_reflection.GeneratedServiceStubType - DESCRIPTOR = _EVENTSCOMPONENTSERVICE + __metaclass__ = reflection.GeneratedProtocolMessageType + DESCRIPTOR = _EVENTRESPONSE + + # @@protoc_insertion_point(class_scope:leap.common.events.EventResponse) # @@protoc_insertion_point(module_scope) -- cgit v1.2.3 From 1ab74ac4f032114f775b6a44392e293b79dad686 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 10 Apr 2013 21:41:48 +0900 Subject: Add option to compile services (needed from 2.4.0 onwards, since it defaults to false) --- setup.py | 2 +- src/leap/common/events/events.proto | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index c0bd98f..269d755 100644 --- a/setup.py +++ b/setup.py @@ -42,7 +42,7 @@ dependency_links = [ setup( name='leap.common', - version='0.2.0-dev', + version='0.2.1-dev', url='https://leap.se/', license='GPLv3+', author='The LEAP Encryption Access Project', diff --git a/src/leap/common/events/events.proto b/src/leap/common/events/events.proto index eb6b98f..447b038 100644 --- a/src/leap/common/events/events.proto +++ b/src/leap/common/events/events.proto @@ -1,5 +1,5 @@ // signal.proto -// Copyright (C) 2013 LEA +// Copyright (C) 2013 LEAP // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -15,6 +15,7 @@ // along with this program. If not, see . package leap.common.events; +option py_generic_services = true; enum Event { CLIENT_SESSION_ID = 1; -- cgit v1.2.3 From 577a3341e5c09f40bddf8a3723818941c32fa5c1 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 10 Apr 2013 21:43:24 +0900 Subject: Add compiled proto bumped version --- setup.py | 2 + src/leap/common/__init__.py | 4 +- src/leap/common/events/events_pb2.py | 295 ++++++++++++++++++++++------------- 3 files changed, 189 insertions(+), 112 deletions(-) diff --git a/setup.py b/setup.py index 269d755..c7bdd76 100644 --- a/setup.py +++ b/setup.py @@ -42,6 +42,8 @@ dependency_links = [ setup( name='leap.common', + # If you change version, do it also in + # src/leap/common/__init__.py version='0.2.1-dev', url='https://leap.se/', license='GPLv3+', diff --git a/src/leap/common/__init__.py b/src/leap/common/__init__.py index 5702ca1..19c43c9 100644 --- a/src/leap/common/__init__.py +++ b/src/leap/common/__init__.py @@ -11,9 +11,9 @@ try: import pygeoip HAS_GEOIP = True except ImportError: - logger.debug('PyGeoIP not found. Disabled Geo support.') + #logger.debug('PyGeoIP not found. Disabled Geo support.') HAS_GEOIP = False __all__ = ["certs", "check", "files", "events"] -__version__ = "0.2.0-dev" +__version__ = "0.2.1-dev" diff --git a/src/leap/common/events/events_pb2.py b/src/leap/common/events/events_pb2.py index 869378a..a4f1df4 100644 --- a/src/leap/common/events/events_pb2.py +++ b/src/leap/common/events/events_pb2.py @@ -3,79 +3,80 @@ from google.protobuf import descriptor from google.protobuf import message from google.protobuf import reflection +from google.protobuf import service +from google.protobuf import service_reflection from google.protobuf import descriptor_pb2 # @@protoc_insertion_point(imports) - DESCRIPTOR = descriptor.FileDescriptor( - name='events.proto', - package='leap.common.events', - serialized_pb='\n\x0c\x65vents.proto\x12\x12leap.common.events\"\x97\x01\n\rSignalRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0f\n\x07\x63ontent\x18\x02 \x02(\t\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\x12\x12\n\nenc_method\x18\x05 \x01(\t\x12\x16\n\x0e\x65rror_occurred\x18\x06 \x01(\x08\"j\n\x0fRegisterRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0c\n\x04port\x18\x02 \x02(\x05\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\"\x82\x01\n\rEventResponse\x12\x38\n\x06status\x18\x01 \x02(\x0e\x32(.leap.common.events.EventResponse.Status\x12\x0e\n\x06result\x18\x02 \x01(\t\"\'\n\x06Status\x12\x06\n\x02OK\x10\x01\x12\n\n\x06UNAUTH\x10\x02\x12\t\n\x05\x45RROR\x10\x03*\xe7\x02\n\x05\x45vent\x12\x15\n\x11\x43LIENT_SESSION_ID\x10\x01\x12\x0e\n\nCLIENT_UID\x10\x02\x12\x19\n\x15SOLEDAD_CREATING_KEYS\x10\x03\x12\x1e\n\x1aSOLEDAD_DONE_CREATING_KEYS\x10\x04\x12\x1a\n\x16SOLEDAD_UPLOADING_KEYS\x10\x05\x12\x1f\n\x1bSOLEDAD_DONE_UPLOADING_KEYS\x10\x06\x12\x1c\n\x18SOLEDAD_DOWNLOADING_KEYS\x10\x07\x12!\n\x1dSOLEDAD_DONE_DOWNLOADING_KEYS\x10\x08\x12\x1c\n\x18SOLEDAD_NEW_DATA_TO_SYNC\x10\t\x12\x1a\n\x16SOLEDAD_DONE_DATA_SYNC\x10\n\x12\x17\n\x13UPDATER_NEW_UPDATES\x10\x0b\x12\x19\n\x15UPDATER_DONE_UPDATING\x10\x0c\x12\x10\n\x0cRAISE_WINDOW\x10\r2\xb9\x01\n\x13\x45ventsServerService\x12R\n\x08register\x12#.leap.common.events.RegisterRequest\x1a!.leap.common.events.EventResponse\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponse2h\n\x16\x45ventsComponentService\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponse') + name='events.proto', + package='leap.common.events', + serialized_pb='\n\x0c\x65vents.proto\x12\x12leap.common.events\"\x97\x01\n\rSignalRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0f\n\x07\x63ontent\x18\x02 \x02(\t\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\x12\x12\n\nenc_method\x18\x05 \x01(\t\x12\x16\n\x0e\x65rror_occurred\x18\x06 \x01(\x08\"j\n\x0fRegisterRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0c\n\x04port\x18\x02 \x02(\x05\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\"\x82\x01\n\rEventResponse\x12\x38\n\x06status\x18\x01 \x02(\x0e\x32(.leap.common.events.EventResponse.Status\x12\x0e\n\x06result\x18\x02 \x01(\t\"\'\n\x06Status\x12\x06\n\x02OK\x10\x01\x12\n\n\x06UNAUTH\x10\x02\x12\t\n\x05\x45RROR\x10\x03*\xe7\x02\n\x05\x45vent\x12\x15\n\x11\x43LIENT_SESSION_ID\x10\x01\x12\x0e\n\nCLIENT_UID\x10\x02\x12\x19\n\x15SOLEDAD_CREATING_KEYS\x10\x03\x12\x1e\n\x1aSOLEDAD_DONE_CREATING_KEYS\x10\x04\x12\x1a\n\x16SOLEDAD_UPLOADING_KEYS\x10\x05\x12\x1f\n\x1bSOLEDAD_DONE_UPLOADING_KEYS\x10\x06\x12\x1c\n\x18SOLEDAD_DOWNLOADING_KEYS\x10\x07\x12!\n\x1dSOLEDAD_DONE_DOWNLOADING_KEYS\x10\x08\x12\x1c\n\x18SOLEDAD_NEW_DATA_TO_SYNC\x10\t\x12\x1a\n\x16SOLEDAD_DONE_DATA_SYNC\x10\n\x12\x17\n\x13UPDATER_NEW_UPDATES\x10\x0b\x12\x19\n\x15UPDATER_DONE_UPDATING\x10\x0c\x12\x10\n\x0cRAISE_WINDOW\x10\r2\xb9\x01\n\x13\x45ventsServerService\x12R\n\x08register\x12#.leap.common.events.RegisterRequest\x1a!.leap.common.events.EventResponse\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponse2h\n\x16\x45ventsComponentService\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponseB\x03\x90\x01\x01') _EVENT = descriptor.EnumDescriptor( - name='Event', - full_name='leap.common.events.Event', - filename=None, - file=DESCRIPTOR, - values=[ - descriptor.EnumValueDescriptor( - name='CLIENT_SESSION_ID', index=0, number=1, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='CLIENT_UID', index=1, number=2, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='SOLEDAD_CREATING_KEYS', index=2, number=3, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='SOLEDAD_DONE_CREATING_KEYS', index=3, number=4, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='SOLEDAD_UPLOADING_KEYS', index=4, number=5, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='SOLEDAD_DONE_UPLOADING_KEYS', index=5, number=6, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='SOLEDAD_DOWNLOADING_KEYS', index=6, number=7, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='SOLEDAD_DONE_DOWNLOADING_KEYS', index=7, number=8, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='SOLEDAD_NEW_DATA_TO_SYNC', index=8, number=9, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='SOLEDAD_DONE_DATA_SYNC', index=9, number=10, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='UPDATER_NEW_UPDATES', index=10, number=11, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='UPDATER_DONE_UPDATING', index=11, number=12, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='RAISE_WINDOW', index=12, number=13, - options=None, - type=None), - ], - containing_type=None, - options=None, - serialized_start=432, - serialized_end=791, + name='Event', + full_name='leap.common.events.Event', + filename=None, + file=DESCRIPTOR, + values=[ + descriptor.EnumValueDescriptor( + name='CLIENT_SESSION_ID', index=0, number=1, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='CLIENT_UID', index=1, number=2, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_CREATING_KEYS', index=2, number=3, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_DONE_CREATING_KEYS', index=3, number=4, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_UPLOADING_KEYS', index=4, number=5, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_DONE_UPLOADING_KEYS', index=5, number=6, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_DOWNLOADING_KEYS', index=6, number=7, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_DONE_DOWNLOADING_KEYS', index=7, number=8, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_NEW_DATA_TO_SYNC', index=8, number=9, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_DONE_DATA_SYNC', index=9, number=10, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='UPDATER_NEW_UPDATES', index=10, number=11, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='UPDATER_DONE_UPDATING', index=11, number=12, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='RAISE_WINDOW', index=12, number=13, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=432, + serialized_end=791, ) @@ -95,42 +96,42 @@ RAISE_WINDOW = 13 _EVENTRESPONSE_STATUS = descriptor.EnumDescriptor( - name='Status', - full_name='leap.common.events.EventResponse.Status', - filename=None, - file=DESCRIPTOR, - values=[ - descriptor.EnumValueDescriptor( - name='OK', index=0, number=1, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='UNAUTH', index=1, number=2, - options=None, - type=None), - descriptor.EnumValueDescriptor( - name='ERROR', index=2, number=3, - options=None, - type=None), - ], - containing_type=None, - options=None, - serialized_start=390, - serialized_end=429, + name='Status', + full_name='leap.common.events.EventResponse.Status', + filename=None, + file=DESCRIPTOR, + values=[ + descriptor.EnumValueDescriptor( + name='OK', index=0, number=1, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='UNAUTH', index=1, number=2, + options=None, + type=None), + descriptor.EnumValueDescriptor( + name='ERROR', index=2, number=3, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=390, + serialized_end=429, ) _SIGNALREQUEST = descriptor.Descriptor( - name='SignalRequest', - full_name='leap.common.events.SignalRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - descriptor.FieldDescriptor( - name='event', full_name='leap.common.events.SignalRequest.event', index=0, - number=1, type=14, cpp_type=8, label=2, - has_default_value=False, default_value=1, + name='SignalRequest', + full_name='leap.common.events.SignalRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + descriptor.FieldDescriptor( + name='event', full_name='leap.common.events.SignalRequest.event', index=0, + number=1, type=14, cpp_type=8, label=2, + has_default_value=False, default_value=1, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), @@ -275,22 +276,96 @@ DESCRIPTOR.message_types_by_name['SignalRequest'] = _SIGNALREQUEST DESCRIPTOR.message_types_by_name['RegisterRequest'] = _REGISTERREQUEST DESCRIPTOR.message_types_by_name['EventResponse'] = _EVENTRESPONSE + class SignalRequest(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _SIGNALREQUEST - - # @@protoc_insertion_point(class_scope:leap.common.events.SignalRequest) + __metaclass__ = reflection.GeneratedProtocolMessageType + DESCRIPTOR = _SIGNALREQUEST + + # @@protoc_insertion_point(class_scope:leap.common.events.SignalRequest) + class RegisterRequest(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _REGISTERREQUEST - - # @@protoc_insertion_point(class_scope:leap.common.events.RegisterRequest) + __metaclass__ = reflection.GeneratedProtocolMessageType + DESCRIPTOR = _REGISTERREQUEST + + # @@protoc_insertion_point(class_scope:leap.common.events.RegisterRequest) + class EventResponse(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _EVENTRESPONSE - - # @@protoc_insertion_point(class_scope:leap.common.events.EventResponse) + __metaclass__ = reflection.GeneratedProtocolMessageType + DESCRIPTOR = _EVENTRESPONSE + + # @@protoc_insertion_point(class_scope:leap.common.events.EventResponse) + + +_EVENTSSERVERSERVICE = descriptor.ServiceDescriptor( + name='EventsServerService', + full_name='leap.common.events.EventsServerService', + file=DESCRIPTOR, + index=0, + options=None, + serialized_start=794, + serialized_end=979, + methods=[ + descriptor.MethodDescriptor( + name='register', + full_name='leap.common.events.EventsServerService.register', + index=0, + containing_service=None, + input_type=_REGISTERREQUEST, + output_type=_EVENTRESPONSE, + options=None, + ), + descriptor.MethodDescriptor( + name='signal', + full_name='leap.common.events.EventsServerService.signal', + index=1, + containing_service=None, + input_type=_SIGNALREQUEST, + output_type=_EVENTRESPONSE, + options=None, + ), +]) + + +class EventsServerService(service.Service): + __metaclass__ = service_reflection.GeneratedServiceType + DESCRIPTOR = _EVENTSSERVERSERVICE + + +class EventsServerService_Stub(EventsServerService): + __metaclass__ = service_reflection.GeneratedServiceStubType + DESCRIPTOR = _EVENTSSERVERSERVICE + + +_EVENTSCOMPONENTSERVICE = descriptor.ServiceDescriptor( + name='EventsComponentService', + full_name='leap.common.events.EventsComponentService', + file=DESCRIPTOR, + index=1, + options=None, + serialized_start=981, + serialized_end=1085, + methods=[ + descriptor.MethodDescriptor( + name='signal', + full_name='leap.common.events.EventsComponentService.signal', + index=0, + containing_service=None, + input_type=_SIGNALREQUEST, + output_type=_EVENTRESPONSE, + options=None, + ), +]) + + +class EventsComponentService(service.Service): + __metaclass__ = service_reflection.GeneratedServiceType + DESCRIPTOR = _EVENTSCOMPONENTSERVICE + + +class EventsComponentService_Stub(EventsComponentService): + __metaclass__ = service_reflection.GeneratedServiceStubType + DESCRIPTOR = _EVENTSCOMPONENTSERVICE # @@protoc_insertion_point(module_scope) -- cgit v1.2.3 From 1ae67202f1878106a3a9770d22dcb695f604d5ee Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 10 Apr 2013 21:44:19 +0900 Subject: Add the right name for pypi package --- setup.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/setup.py b/setup.py index c7bdd76..3c0f9de 100644 --- a/setup.py +++ b/setup.py @@ -31,10 +31,7 @@ requirements = [ dependency_links = [ - # XXX this link is only for py2.6??? - # we need to get this packaged or included - #"https://protobuf-socket-rpc.googlecode.com/files/protobuf.socketrpc-1.3.2-py2.6.egg#egg=protobuf.socketrpc", - "https://code.google.com/p/protobuf-socket-rpc/downloads/detail?name=protobuf.socketrpc-1.3.2.tar.gz#egg=protobuf.socketrpc", + "https://protobuf-socket-rpc.googlecode.com/files/protobuf.socketrpc-1.3.2.tar.gz" ] -- cgit v1.2.3 From 1858a50809dc48bfcc4ad2d96dd5641f1de6b9eb Mon Sep 17 00:00:00 2001 From: drebs Date: Sun, 14 Apr 2013 14:15:10 -0300 Subject: Add key manager basic API docstrings. --- changes/feature_key-manager | 1 + src/leap/common/keymanager/__init__.py | 232 +++++++++++++++++++++++++++++++++ 2 files changed, 233 insertions(+) create mode 100644 changes/feature_key-manager create mode 100644 src/leap/common/keymanager/__init__.py diff --git a/changes/feature_key-manager b/changes/feature_key-manager new file mode 100644 index 0000000..6588dde --- /dev/null +++ b/changes/feature_key-manager @@ -0,0 +1 @@ + o Add a Key Manager. diff --git a/src/leap/common/keymanager/__init__.py b/src/leap/common/keymanager/__init__.py new file mode 100644 index 0000000..71aaddd --- /dev/null +++ b/src/leap/common/keymanager/__init__.py @@ -0,0 +1,232 @@ +# -*- coding: utf-8 -*- +# __init__.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +""" +Key Manager is a Nicknym agent for LEAP client. +""" + + +try: + import simplejson as json +except ImportError: + import json # noqa + + +from abc import ABCMeta, abstractmethod +from u1db.errors import HTTPError + + +# +# Key types +# + +class EncryptionKey(object): + """ + Abstract class for encryption keys. + + A key is "validated" if the nicknym agent has bound the user address to a + public key. Nicknym supports three different levels of key validation: + + * Level 3 - path trusted: A path of cryptographic signatures can be traced + from a trusted key to the key under evaluation. By default, only the + provider key from the user's provider is a "trusted key". + * level 2 - provider signed: The key has been signed by a provider key for + the same domain, but the provider key is not validated using a trust + path (i.e. it is only registered) + * level 1 - registered: The key has been encountered and saved, it has no + signatures (that are meaningful to the nicknym agent). + """ + + __metaclass__ = ABCMeta + + def __init__(self, address, key_id=None, fingerprint=None, + key_data=None, length=None, expiry_date=None, + validation=None, first_seen_at=None, + last_audited_at=None): + self.address = address + self.key_id = key_id + self.fingerprint = fingerprint + self.key_data = key_data + self.length = length + self.expiry_date = expiry_date + self.validation = validation + self.first_seen_at = first_seen_at + self.last_audited_at = last_audited_at + + @abstractmethod + def get_json(self): + """ + Return a JSON string describing this key. + + @return: The JSON string describing this key. + @rtype: str + """ + + +# +# Key wrappers +# + +class KeyTypeWrapper(object): + """ + Abstract class for Key Type Wrappers. + + A wrapper for a certain key type should know how to get and put keys in + local storage using Soledad and also how to generate new keys. + """ + + __metaclass__ = ABCMeta + + @abstractmethod + def get_key(self, address): + """ + Get key from local storage. + + @param address: The address bound to the key. + @type address: str + + @return: The key bound to C{address}. + @rtype: EncryptionKey + @raise KeyNotFound: If the key was not found on local storage. + """ + + @abstractmethod + def put_key(self, key): + """ + Put a key in local storage. + + @param key: The key to be stored. + @type key: EncryptionKey + """ + + @abstractmethod + def gen_key(self, address): + """ + Generate a new key. + + @param address: The address bound to the key. + @type address: str + @return: The key bound to C{address}. + @rtype: EncryptionKey + """ + + +# +# Key manager +# + + +class KeyNotFound(Exception): + """ + Raised when key was no found on keyserver. + """ + + +class KeyManager(object): + + def __init__(self, address, url): + """ + Initialize a Key Manager for user's C{address} with provider's + nickserver reachable in C{url}. + + @param address: The address of the user of this Key Manager. + @type address: str + @param url: The URL of the key manager. + @type url: str + """ + self.address = address + self.url = url + + def send_key(self, ktype, send_private=False, password=None): + """ + Send user's key of type C{ktype} to provider. + + Public key bound to user's is sent to provider, which will sign it and + replace any prior keys for the same address in its database. + + If C{send_private} is True, then the private key is encrypted with + C{password} and sent to server in the same request, together with a + hash string of user's address and password. The encrypted private key + will be saved in the server in a way it is publicly retrievable + through the hash string. + + @param address: The address bound to the key. + @type address: str + @param ktype: The type of the key. + @type ktype: KeyType + + @raise httplib.HTTPException: + """ + + def get_key(self, address, ktype): + """ + Return a key of type C{ktype} bound to C{address}. + + First, search for the key in local storage. If it is not available, + then try to fetch from nickserver. + + @param address: The address bound to the key. + @type address: str + @param ktype: The type of the key. + @type ktype: KeyType + + @return: A key of type C{ktype} bound to C{address}. + @rtype: EncryptionKey + @raise KeyNotFound: If the key was not found both locally and in + keyserver. + """ + try: + return wrapper_map[ktype].get_key(address) + except KeyNotFound: + key = filter(lambda k: isinstance(k, ktype), + self._fetch_keys(address)) + if key is None + raise KeyNotFound() + wrapper_map[ktype].put_key(key) + return key + + + def _fetch_keys(self, address): + """ + Fetch keys bound to C{address} from nickserver. + + @param address: The address bound to the keys. + @type address: str + + @return: A list of keys bound to C{address}. + @rtype: list of EncryptionKey + @raise KeyNotFound: If the key was not found on nickserver. + @raise httplib.HTTPException: + """ + + def refresh_keys(self): + """ + Update the user's db of validated keys to see if there are changes. + """ + + def gen_key(self, ktype): + """ + Generate a key of type C{ktype} bound to the user's address. + + @param ktype: The type of the key. + @type ktype: KeyType + + @return: The generated key. + @rtype: EncryptionKey + """ + return wrapper_map[ktype].gen_key(self.address) -- cgit v1.2.3 From 314bc876d564cd6265cc8eb4095e423f1140349a Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 15 Apr 2013 10:41:56 -0300 Subject: Add basic openpgp key handling to Key Manager --- src/leap/common/keymanager/__init__.py | 116 +-------- src/leap/common/keymanager/errors.py | 29 +++ src/leap/common/keymanager/gpg.py | 398 +++++++++++++++++++++++++++++++ src/leap/common/keymanager/keys.py | 127 ++++++++++ src/leap/common/keymanager/openpgp.py | 126 ++++++++++ src/leap/common/tests/test_keymanager.py | 230 ++++++++++++++++++ 6 files changed, 922 insertions(+), 104 deletions(-) create mode 100644 src/leap/common/keymanager/errors.py create mode 100644 src/leap/common/keymanager/gpg.py create mode 100644 src/leap/common/keymanager/keys.py create mode 100644 src/leap/common/keymanager/openpgp.py create mode 100644 src/leap/common/tests/test_keymanager.py diff --git a/src/leap/common/keymanager/__init__.py b/src/leap/common/keymanager/__init__.py index 71aaddd..10acb36 100644 --- a/src/leap/common/keymanager/__init__.py +++ b/src/leap/common/keymanager/__init__.py @@ -27,114 +27,22 @@ except ImportError: import json # noqa -from abc import ABCMeta, abstractmethod from u1db.errors import HTTPError -# -# Key types -# - -class EncryptionKey(object): - """ - Abstract class for encryption keys. - - A key is "validated" if the nicknym agent has bound the user address to a - public key. Nicknym supports three different levels of key validation: - - * Level 3 - path trusted: A path of cryptographic signatures can be traced - from a trusted key to the key under evaluation. By default, only the - provider key from the user's provider is a "trusted key". - * level 2 - provider signed: The key has been signed by a provider key for - the same domain, but the provider key is not validated using a trust - path (i.e. it is only registered) - * level 1 - registered: The key has been encountered and saved, it has no - signatures (that are meaningful to the nicknym agent). - """ - - __metaclass__ = ABCMeta - - def __init__(self, address, key_id=None, fingerprint=None, - key_data=None, length=None, expiry_date=None, - validation=None, first_seen_at=None, - last_audited_at=None): - self.address = address - self.key_id = key_id - self.fingerprint = fingerprint - self.key_data = key_data - self.length = length - self.expiry_date = expiry_date - self.validation = validation - self.first_seen_at = first_seen_at - self.last_audited_at = last_audited_at - - @abstractmethod - def get_json(self): - """ - Return a JSON string describing this key. - - @return: The JSON string describing this key. - @rtype: str - """ - - -# -# Key wrappers -# - -class KeyTypeWrapper(object): - """ - Abstract class for Key Type Wrappers. - - A wrapper for a certain key type should know how to get and put keys in - local storage using Soledad and also how to generate new keys. - """ - - __metaclass__ = ABCMeta - - @abstractmethod - def get_key(self, address): - """ - Get key from local storage. - - @param address: The address bound to the key. - @type address: str - - @return: The key bound to C{address}. - @rtype: EncryptionKey - @raise KeyNotFound: If the key was not found on local storage. - """ - - @abstractmethod - def put_key(self, key): - """ - Put a key in local storage. - - @param key: The key to be stored. - @type key: EncryptionKey - """ - - @abstractmethod - def gen_key(self, address): - """ - Generate a new key. - - @param address: The address bound to the key. - @type address: str - @return: The key bound to C{address}. - @rtype: EncryptionKey - """ - - -# -# Key manager -# +from leap.common.keymanager.errors import ( + KeyNotFound, + KeyAlreadyExists, +) +from leap.common.keymanager.openpgp import ( + OpenPGPKey, + OpenPGPWrapper, +) -class KeyNotFound(Exception): - """ - Raised when key was no found on keyserver. - """ +wrapper_map = { + OpenPGPKey: OpenPGPWrapper(), +} class KeyManager(object): @@ -195,7 +103,7 @@ class KeyManager(object): except KeyNotFound: key = filter(lambda k: isinstance(k, ktype), self._fetch_keys(address)) - if key is None + if key is None: raise KeyNotFound() wrapper_map[ktype].put_key(key) return key diff --git a/src/leap/common/keymanager/errors.py b/src/leap/common/keymanager/errors.py new file mode 100644 index 0000000..f5bb1ab --- /dev/null +++ b/src/leap/common/keymanager/errors.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# errors.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + + +class KeyNotFound(Exception): + """ + Raised when key was no found on keyserver. + """ + + +class KeyAlreadyExists(Exception): + """ + Raised when attempted to create a key that already exists. + """ diff --git a/src/leap/common/keymanager/gpg.py b/src/leap/common/keymanager/gpg.py new file mode 100644 index 0000000..dc5d791 --- /dev/null +++ b/src/leap/common/keymanager/gpg.py @@ -0,0 +1,398 @@ +# -*- coding: utf-8 -*- +# gpgwrapper.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +""" +A GPG wrapper used to handle OpenPGP keys. + +This is a temporary class that will be superseded by the a revised version of +python-gnupg. +""" + + +import os +import gnupg +import re +from gnupg import ( + logger, + _is_sequence, + _make_binary_stream, +) + + +class ListPackets(): + """ + Handle status messages for --list-packets. + """ + + def __init__(self, gpg): + """ + Initialize the packet listing handling class. + + @param gpg: GPG object instance. + @type gpg: gnupg.GPG + """ + self.gpg = gpg + self.nodata = None + self.key = None + self.need_passphrase = None + self.need_passphrase_sym = None + self.userid_hint = None + + def handle_status(self, key, value): + """ + Handle one line of the --list-packets status message. + + @param key: The status message key. + @type key: str + @param value: The status message value. + @type value: str + """ + # TODO: write tests for handle_status + if key == 'NODATA': + self.nodata = True + if key == 'ENC_TO': + # This will only capture keys in our keyring. In the future we + # may want to include multiple unknown keys in this list. + self.key, _, _ = value.split() + if key == 'NEED_PASSPHRASE': + self.need_passphrase = True + if key == 'NEED_PASSPHRASE_SYM': + self.need_passphrase_sym = True + if key == 'USERID_HINT': + self.userid_hint = value.strip().split() + + +class GPGWrapper(gnupg.GPG): + """ + This is a temporary class for handling GPG requests, and should be + replaced by a more general class used throughout the project. + """ + + GNUPG_HOME = os.environ['HOME'] + "/.config/leap/gnupg" + GNUPG_BINARY = "/usr/bin/gpg" # this has to be changed based on OS + + def __init__(self, gpgbinary=GNUPG_BINARY, gnupghome=GNUPG_HOME, + verbose=False, use_agent=False, keyring=None, options=None): + """ + Initialize a GnuPG process wrapper. + + @param gpgbinary: Name for GnuPG binary executable. + @type gpgbinary: C{str} + @param gpghome: Full pathname to directory containing the public and + private keyrings. + @type gpghome: C{str} + @param keyring: Name of alternative keyring file to use. If specified, + the default keyring is not used. + @param verbose: Should some verbose info be output? + @type verbose: bool + @param use_agent: Should pass `--use-agent` to GPG binary? + @type use_agent: bool + @param keyring: Path for the keyring to use. + @type keyring: str + @options: A list of additional options to pass to the GPG binary. + @type options: list + + @raise: RuntimeError with explanation message if there is a problem + invoking gpg. + """ + gnupg.GPG.__init__(self, gnupghome=gnupghome, gpgbinary=gpgbinary, + verbose=verbose, use_agent=use_agent, + keyring=keyring, options=options) + self.result_map['list-packets'] = ListPackets + + def find_key_by_email(self, email, secret=False): + """ + Find user's key based on their email. + + @param email: Email address of key being searched for. + @type email: str + @param secret: Should we search for a secret key? + @type secret: bool + + @return: The fingerprint of the found key. + @rtype: str + """ + for key in self.list_keys(secret=secret): + for uid in key['uids']: + if re.search(email, uid): + return key + raise LookupError("GnuPG public key for email %s not found!" % email) + + def find_key_by_subkey(self, subkey, secret=False): + """ + Find user's key based on a subkey fingerprint. + + @param email: Subkey fingerprint of the key being searched for. + @type email: str + @param secret: Should we search for a secret key? + @type secret: bool + + @return: The fingerprint of the found key. + @rtype: str + """ + for key in self.list_keys(secret=secret): + for sub in key['subkeys']: + if sub[0] == subkey: + return key + raise LookupError( + "GnuPG public key for subkey %s not found!" % subkey) + + def find_key_by_keyid(self, keyid, secret=False): + """ + Find user's key based on the key ID. + + @param email: The key ID of the key being searched for. + @type email: str + @param secret: Should we search for a secret key? + @type secret: bool + + @return: The fingerprint of the found key. + @rtype: str + """ + for key in self.list_keys(secret=secret): + if keyid == key['keyid']: + return key + raise LookupError( + "GnuPG public key for keyid %s not found!" % keyid) + + def find_key_by_fingerprint(self, fingerprint, secret=False): + """ + Find user's key based on the key fingerprint. + + @param email: The fingerprint of the key being searched for. + @type email: str + @param secret: Should we search for a secret key? + @type secret: bool + + @return: The fingerprint of the found key. + @rtype: str + """ + for key in self.list_keys(secret=secret): + if fingerprint == key['fingerprint']: + return key + raise LookupError( + "GnuPG public key for fingerprint %s not found!" % fingerprint) + + def encrypt(self, data, recipient, sign=None, always_trust=True, + passphrase=None, symmetric=False): + """ + Encrypt data using GPG. + + @param data: The data to be encrypted. + @type data: str + @param recipient: The address of the public key to be used. + @type recipient: str + @param sign: Should the encrypted content be signed? + @type sign: bool + @param always_trust: Skip key validation and assume that used keys + are always fully trusted? + @type always_trust: bool + @param passphrase: The passphrase to be used if symmetric encryption + is desired. + @type passphrase: str + @param symmetric: Should we encrypt to a password? + @type symmetric: bool + + @return: An object with encrypted result in the `data` field. + @rtype: gnupg.Crypt + """ + # TODO: devise a way so we don't need to "always trust". + return gnupg.GPG.encrypt(self, data, recipient, sign=sign, + always_trust=always_trust, + passphrase=passphrase, + symmetric=symmetric, + cipher_algo='AES256') + + def decrypt(self, data, always_trust=True, passphrase=None): + """ + Decrypt data using GPG. + + @param data: The data to be decrypted. + @type data: str + @param always_trust: Skip key validation and assume that used keys + are always fully trusted? + @type always_trust: bool + @param passphrase: The passphrase to be used if symmetric encryption + is desired. + @type passphrase: str + + @return: An object with decrypted result in the `data` field. + @rtype: gnupg.Crypt + """ + # TODO: devise a way so we don't need to "always trust". + return gnupg.GPG.decrypt(self, data, always_trust=always_trust, + passphrase=passphrase) + + def send_keys(self, keyserver, *keyids): + """ + Send keys to a keyserver + + @param keyserver: The keyserver to send the keys to. + @type keyserver: str + @param keyids: The key ids to send. + @type keyids: list + + @return: A list of keys sent to server. + @rtype: gnupg.ListKeys + """ + # TODO: write tests for this. + # TODO: write a SendKeys class to handle status for this. + result = self.result_map['list'](self) + gnupg.logger.debug('send_keys: %r', keyids) + data = gnupg._make_binary_stream("", self.encoding) + args = ['--keyserver', keyserver, '--send-keys'] + args.extend(keyids) + self._handle_io(args, data, result, binary=True) + gnupg.logger.debug('send_keys result: %r', result.__dict__) + data.close() + return result + + def encrypt_file(self, file, recipients, sign=None, + always_trust=False, passphrase=None, + armor=True, output=None, symmetric=False, + cipher_algo=None): + """ + Encrypt the message read from the file-like object 'file'. + + @param file: The file to be encrypted. + @type data: file + @param recipient: The address of the public key to be used. + @type recipient: str + @param sign: Should the encrypted content be signed? + @type sign: bool + @param always_trust: Skip key validation and assume that used keys + are always fully trusted? + @type always_trust: bool + @param passphrase: The passphrase to be used if symmetric encryption + is desired. + @type passphrase: str + @param armor: Create ASCII armored output? + @type armor: bool + @param output: Path of file to write results in. + @type output: str + @param symmetric: Should we encrypt to a password? + @type symmetric: bool + @param cipher_algo: Algorithm to use. + @type cipher_algo: str + + @return: An object with encrypted result in the `data` field. + @rtype: gnupg.Crypt + """ + args = ['--encrypt'] + if symmetric: + args = ['--symmetric'] + if cipher_algo: + args.append('--cipher-algo %s' % cipher_algo) + else: + args = ['--encrypt'] + if not _is_sequence(recipients): + recipients = (recipients,) + for recipient in recipients: + args.append('--recipient "%s"' % recipient) + if armor: # create ascii-armored output - set to False for binary + args.append('--armor') + if output: # write the output to a file with the specified name + if os.path.exists(output): + os.remove(output) # to avoid overwrite confirmation message + args.append('--output "%s"' % output) + if sign: + args.append('--sign --default-key "%s"' % sign) + if always_trust: + args.append("--always-trust") + result = self.result_map['crypt'](self) + self._handle_io(args, file, result, passphrase=passphrase, binary=True) + logger.debug('encrypt result: %r', result.data) + return result + + def list_packets(self, data): + """ + List the sequence of packets. + + @param data: The data to extract packets from. + @type data: str + + @return: An object with packet info. + @rtype ListPackets + """ + args = ["--list-packets"] + result = self.result_map['list-packets'](self) + self._handle_io( + args, + _make_binary_stream(data, self.encoding), + result, + ) + return result + + def encrypted_to(self, data): + """ + Return the key to which data is encrypted to. + + @param data: The data to be examined. + @type data: str + + @return: The fingerprint of the key to which data is encrypted to. + @rtype: str + """ + # TODO: make this support multiple keys. + result = self.list_packets(data) + if not result.key: + raise LookupError( + "Content is not encrypted to a GnuPG key!") + try: + return self.find_key_by_keyid(result.key) + except: + return self.find_key_by_subkey(result.key) + + def is_encrypted_sym(self, data): + """ + Say whether some chunk of data is encrypted to a symmetric key. + + @param data: The data to be examined. + @type data: str + + @return: Whether data is encrypted to a symmetric key. + @rtype: bool + """ + result = self.list_packets(data) + return bool(result.need_passphrase_sym) + + def is_encrypted_asym(self, data): + """ + Say whether some chunk of data is encrypted to a private key. + + @param data: The data to be examined. + @type data: str + + @return: Whether data is encrypted to a private key. + @rtype: bool + """ + result = self.list_packets(data) + return bool(result.key) + + def is_encrypted(self, data): + """ + Say whether some chunk of data is encrypted to a key. + + @param data: The data to be examined. + @type data: str + + @return: Whether data is encrypted to a key. + @rtype: bool + """ + self.is_encrypted_asym() or self.is_encrypted_sym() + diff --git a/src/leap/common/keymanager/keys.py b/src/leap/common/keymanager/keys.py new file mode 100644 index 0000000..13e3c0b --- /dev/null +++ b/src/leap/common/keymanager/keys.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- +# keys.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +""" +Abstact key type and wrapper representations. +""" + + +from abc import ABCMeta, abstractmethod + + +class EncryptionKey(object): + """ + Abstract class for encryption keys. + + A key is "validated" if the nicknym agent has bound the user address to a + public key. Nicknym supports three different levels of key validation: + + * Level 3 - path trusted: A path of cryptographic signatures can be traced + from a trusted key to the key under evaluation. By default, only the + provider key from the user's provider is a "trusted key". + * level 2 - provider signed: The key has been signed by a provider key for + the same domain, but the provider key is not validated using a trust + path (i.e. it is only registered) + * level 1 - registered: The key has been encountered and saved, it has no + signatures (that are meaningful to the nicknym agent). + """ + + __metaclass__ = ABCMeta + + def __init__(self, address, key_id=None, fingerprint=None, + key_data=None, length=None, expiry_date=None, + validation=None, first_seen_at=None, + last_audited_at=None): + self.address = address + self.key_id = key_id + self.fingerprint = fingerprint + self.key_data = key_data + self.length = length + self.expiry_date = expiry_date + self.validation = validation + self.first_seen_at = first_seen_at + self.last_audited_at = last_audited_at + + def get_json(self): + """ + Return a JSON string describing this key. + + @return: The JSON string describing this key. + @rtype: str + """ + return json.dumps({ + 'address': self.address, + 'type': str(self.__type__), + 'key_id': self.key_id, + 'fingerprint': self.fingerprint, + 'key_data': self.key_data, + 'length': self.length, + 'expiry_date': self.expiry_date, + 'validation': self.validation, + 'first_seen_at': self.first_seen_at, + 'last_audited_at': self.last_audited_at, + }) + + +# +# Key wrappers +# + +class KeyTypeWrapper(object): + """ + Abstract class for Key Type Wrappers. + + A wrapper for a certain key type should know how to get and put keys in + local storage using Soledad and also how to generate new keys. + """ + + __metaclass__ = ABCMeta + + @abstractmethod + def get_key(self, address): + """ + Get key from local storage. + + @param address: The address bound to the key. + @type address: str + + @return: The key bound to C{address}. + @rtype: EncryptionKey + @raise KeyNotFound: If the key was not found on local storage. + """ + + @abstractmethod + def put_key(self, key): + """ + Put a key in local storage. + + @param key: The key to be stored. + @type key: EncryptionKey + """ + + @abstractmethod + def gen_key(self, address): + """ + Generate a new key. + + @param address: The address bound to the key. + @type address: str + @return: The key bound to C{address}. + @rtype: EncryptionKey + """ + diff --git a/src/leap/common/keymanager/openpgp.py b/src/leap/common/keymanager/openpgp.py new file mode 100644 index 0000000..bb73089 --- /dev/null +++ b/src/leap/common/keymanager/openpgp.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +# openpgpwrapper.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +""" +Infrastructure for using OpenPGP keys in Key Manager. +""" + + +import re + +from leap.common.keymanager.errors import ( + KeyNotFound, + KeyAlreadyExists, +) +from leap.common.keymanager.keys import ( + EncryptionKey, + KeyTypeWrapper, +) +from leap.common.keymanager.gpg import GPGWrapper + + +class OpenPGPKey(EncryptionKey): + """ + Base class for OpenPGP keys. + """ + + +class OpenPGPWrapper(KeyTypeWrapper): + """ + A wrapper for OpenPGP keys. + """ + + def __init__(self, gnupghome=None): + self._gpg = GPGWrapper(gnupghome=gnupghome) + + def _build_key(self, address, result): + """ + Build an OpenPGPWrapper key for C{address} based on C{result} from + local storage. + + @param address: The address bound to the key. + @type address: str + @param result: Result obtained from GPG storage. + @type result: dict + """ + key_data = self._gpg.export_keys(result['fingerprint'], secret=False) + return OpenPGPKey( + address, + key_id=result['keyid'], + fingerprint=result['fingerprint'], + key_data=key_data, + length=result['length'], + expiry_date=result['expires'], + validation=None, # TODO: verify for validation. + ) + + def gen_key(self, address): + """ + Generate an OpenPGP keypair for C{address}. + + @param address: The address bound to the key. + @type address: str + @return: The key bound to C{address}. + @rtype: OpenPGPKey + @raise KeyAlreadyExists: If key already exists in local database. + """ + try: + self.get_key(address) + raise KeyAlreadyExists() + except KeyNotFound: + pass + params = self._gpg.gen_key_input( + key_type='RSA', + key_length=4096, + name_real=address, + name_email=address, + name_comment='Generated by LEAP Key Manager.') + self._gpg.gen_key(params) + return self.get_key(address) + + def get_key(self, address): + """ + Get key bound to C{address} from local storage. + + @param address: The address bound to the key. + @type address: str + + @return: The key bound to C{address}. + @rtype: OpenPGPKey + @raise KeyNotFound: If the key was not found on local storage. + """ + m = re.compile('.*<%s>$' % address) + keys = self._gpg.list_keys(secret=False) + + def bound_to_address(key): + return bool(filter(lambda u: m.match(u), key['uids'])) + + try: + bound_key = filter(bound_to_address, keys).pop() + return self._build_key(address, bound_key) + except IndexError: + raise KeyNotFound(address) + + def put_key(self, data): + """ + Put key contained in {data} in local storage. + + @param key: The key data to be stored. + @type key: str + """ + self._gpg.import_keys(data) diff --git a/src/leap/common/tests/test_keymanager.py b/src/leap/common/tests/test_keymanager.py new file mode 100644 index 0000000..4189aac --- /dev/null +++ b/src/leap/common/tests/test_keymanager.py @@ -0,0 +1,230 @@ +## -*- coding: utf-8 -*- +# test_keymanager.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +""" +Tests for the Key Manager. +""" + + +import unittest + + +from leap.common.testing.basetest import BaseLeapTest +from leap.common.keymanager import KeyManager, openpgp, KeyNotFound + + +class KeyManagerTestCase(BaseLeapTest): + + def setUp(self): + pass + + def tearDown(self): + pass + + def _key_manager(user='user@leap.se', url='https://domain.org:6425'): + return KeyManager(user, url) + + def test_openpgp_gen_key(self): + pgp = openpgp.OpenPGPWrapper(self.tempdir+'/gnupg') + try: + pgp.get_key('user@leap.se') + except KeyNotFound: + key = pgp.gen_key('user@leap.se') + self.assertIsInstance(key, openpgp.OpenPGPKey) + self.assertEqual( + 'user@leap.se', key.address, 'Wrong address bound to key.') + self.assertEqual( + '4096', key.length, 'Wrong key length.') + + def test_openpgp_put_key(self): + pgp = openpgp.OpenPGPWrapper(self.tempdir+'/gnupg2') + try: + pgp.get_key('leap@leap.se') + except KeyNotFound: + pgp.put_key(PUBLIC_KEY) + key = pgp.get_key('leap@leap.se') + self.assertIsInstance(key, openpgp.OpenPGPKey) + self.assertEqual( + 'leap@leap.se', key.address, 'Wrong address bound to key.') + self.assertEqual( + '4096', key.length, 'Wrong key length.') + + + +# Key material for testing +KEY_FINGERPRINT = "E36E738D69173C13D709E44F2F455E2824D18DDF" +PUBLIC_KEY = """ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +mQINBFC9+dkBEADNRfwV23TWEoGc/x0wWH1P7PlXt8MnC2Z1kKaKKmfnglVrpOiz +iLWoiU58sfZ0L5vHkzXHXCBf6Eiy/EtUIvdiWAn+yASJ1mk5jZTBKO/WMAHD8wTO +zpMsFmWyg3xc4DkmFa9KQ5EVU0o/nqPeyQxNMQN7px5pPwrJtJFmPxnxm+aDkPYx +irDmz/4DeDNqXliazGJKw7efqBdlwTHkl9Akw2gwy178pmsKwHHEMOBOFFvX61AT +huKqHYmlCGSliwbrJppTG7jc1/ls3itrK+CWTg4txREkSpEVmfcASvw/ZqLbjgfs +d/INMwXnR9U81O8+7LT6yw/ca4ppcFoJD7/XJbkRiML6+bJ4Dakiy6i727BzV17g +wI1zqNvm5rAhtALKfACha6YO43aJzairO4II1wxVHvRDHZn2IuKDDephQ3Ii7/vb +hUOf6XCSmchkAcpKXUOvbxm1yfB1LRa64mMc2RcZxf4mW7KQkulBsdV5QG2276lv +U2UUy2IutXcGP5nXC+f6sJJGJeEToKJ57yiO/VWJFjKN8SvP+7AYsQSqINUuEf6H +T5gCPCraGMkTUTPXrREvu7NOohU78q6zZNaL3GW8ai7eSeANSuQ8Vzffx7Wd8Y7i +Pw9sYj0SMFs1UgjbuL6pO5ueHh+qyumbtAq2K0Bci0kqOcU4E9fNtdiovQARAQAB +tBxMZWFwIFRlc3QgS2V5IDxsZWFwQGxlYXAuc2U+iQI3BBMBCAAhBQJQvfnZAhsD +BQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAAAoJEC9FXigk0Y3fT7EQAKH3IuRniOpb +T/DDIgwwjz3oxB/W0DDMyPXowlhSOuM0rgGfntBpBb3boezEXwL86NPQxNGGruF5 +hkmecSiuPSvOmQlqlS95NGQp6hNG0YaKColh+Q5NTspFXCAkFch9oqUje0LdxfSP +QfV9UpeEvGyPmk1I9EJV/YDmZ4+Djge1d7qhVZInz4Rx1NrSyF/Tc2EC0VpjQFsU +Y9Kb2YBBR7ivG6DBc8ty0jJXi7B4WjkFcUEJviQpMF2dCLdonCehYs1PqsN1N7j+ +eFjQd+hqVMJgYuSGKjvuAEfClM6MQw7+FmFwMyLgK/Ew/DttHEDCri77SPSkOGSI +txCzhTg6798f6mJr7WcXmHX1w1Vcib5FfZ8vTDFVhz/XgAgArdhPo9V6/1dgSSiB +KPQ/spsco6u5imdOhckERE0lnAYvVT6KE81TKuhF/b23u7x+Wdew6kK0EQhYA7wy +7LmlaNXc7rMBQJ9Z60CJ4JDtatBWZ0kNrt2VfdDHVdqBTOpl0CraNUjWE5YMDasr +K2dF5IX8D3uuYtpZnxqg0KzyLg0tzL0tvOL1C2iudgZUISZNPKbS0z0v+afuAAnx +2pTC3uezbh2Jt8SWTLhll4i0P4Ps5kZ6HQUO56O+/Z1cWovX+mQekYFmERySDR9n +3k1uAwLilJmRmepGmvYbB8HloV8HqwgguQINBFC9+dkBEAC0I/xn1uborMgDvBtf +H0sEhwnXBC849/32zic6udB6/3Efk9nzbSpL3FSOuXITZsZgCHPkKarnoQ2ztMcS +sh1ke1C5gQGms75UVmM/nS+2YI4vY8OX/GC/on2vUyncqdH+bR6xH5hx4NbWpfTs +iQHmz5C6zzS/kuabGdZyKRaZHt23WQ7JX/4zpjqbC99DjHcP9BSk7tJ8wI4bkMYD +uFVQdT9O6HwyKGYwUU4sAQRAj7XCTGvVbT0dpgJwH4RmrEtJoHAx4Whg8mJ710E0 +GCmzf2jqkNuOw76ivgk27Kge+Hw00jmJjQhHY0yVbiaoJwcRrPKzaSjEVNgrpgP3 +lXPRGQArgESsIOTeVVHQ8fhK2YtTeCY9rIiO+L0OX2xo9HK7hfHZZWL6rqymXdyS +fhzh/f6IPyHFWnvj7Brl7DR8heMikygcJqv+ed2yx7iLyCUJ10g12I48+aEj1aLe +dP7lna32iY8/Z0SHQLNH6PXO9SlPcq2aFUgKqE75A/0FMk7CunzU1OWr2ZtTLNO1 +WT/13LfOhhuEq9jTyTosn0WxBjJKq18lnhzCXlaw6EAtbA7CUwsD3CTPR56aAXFK +3I7KXOVAqggrvMe5Tpdg5drfYpI8hZovL5aAgb+7Y5ta10TcJdUhS5K3kFAWe/td +U0cmWUMDP1UMSQ5Jg6JIQVWhSwARAQABiQIfBBgBCAAJBQJQvfnZAhsMAAoJEC9F +Xigk0Y3fRwsP/i0ElYCyxeLpWJTwo1iCLkMKz2yX1lFVa9nT1BVTPOQwr/IAc5OX +NdtbJ14fUsKL5pWgW8OmrXtwZm1y4euI1RPWWubG01ouzwnGzv26UcuHeqC5orZj +cOnKtL40y8VGMm8LoicVkRJH8blPORCnaLjdOtmA3rx/v2EXrJpSa3AhOy0ZSRXk +ZSrK68AVNwamHRoBSYyo0AtaXnkPX4+tmO8X8BPfj125IljubvwZPIW9VWR9UqCE +VPfDR1XKegVb6VStIywF7kmrknM1C5qUY28rdZYWgKorw01hBGV4jTW0cqde3N51 +XT1jnIAa+NoXUM9uQoGYMiwrL7vNsLlyyiW5ayDyV92H/rIuiqhFgbJsHTlsm7I8 +oGheR784BagAA1NIKD1qEO9T6Kz9lzlDaeWS5AUKeXrb7ZJLI1TTCIZx5/DxjLqM +Tt/RFBpVo9geZQrvLUqLAMwdaUvDXC2c6DaCPXTh65oCZj/hqzlJHH+RoTWWzKI+ +BjXxgUWF9EmZUBrg68DSmI+9wuDFsjZ51BcqvJwxyfxtTaWhdoYqH/UQS+D1FP3/ +diZHHlzwVwPICzM9ooNTgbrcDzyxRkIVqsVwBq7EtzcvgYUyX53yG25Giy6YQaQ2 +ZtQ/VymwFL3XdUWV6B/hU4PVAFvO3qlOtdJ6TpE+nEWgcWjCv5g7RjXX +=MuOY +-----END PGP PUBLIC KEY BLOCK----- +""" +PRIVATE_KEY = """ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +lQcYBFC9+dkBEADNRfwV23TWEoGc/x0wWH1P7PlXt8MnC2Z1kKaKKmfnglVrpOiz +iLWoiU58sfZ0L5vHkzXHXCBf6Eiy/EtUIvdiWAn+yASJ1mk5jZTBKO/WMAHD8wTO +zpMsFmWyg3xc4DkmFa9KQ5EVU0o/nqPeyQxNMQN7px5pPwrJtJFmPxnxm+aDkPYx +irDmz/4DeDNqXliazGJKw7efqBdlwTHkl9Akw2gwy178pmsKwHHEMOBOFFvX61AT +huKqHYmlCGSliwbrJppTG7jc1/ls3itrK+CWTg4txREkSpEVmfcASvw/ZqLbjgfs +d/INMwXnR9U81O8+7LT6yw/ca4ppcFoJD7/XJbkRiML6+bJ4Dakiy6i727BzV17g +wI1zqNvm5rAhtALKfACha6YO43aJzairO4II1wxVHvRDHZn2IuKDDephQ3Ii7/vb +hUOf6XCSmchkAcpKXUOvbxm1yfB1LRa64mMc2RcZxf4mW7KQkulBsdV5QG2276lv +U2UUy2IutXcGP5nXC+f6sJJGJeEToKJ57yiO/VWJFjKN8SvP+7AYsQSqINUuEf6H +T5gCPCraGMkTUTPXrREvu7NOohU78q6zZNaL3GW8ai7eSeANSuQ8Vzffx7Wd8Y7i +Pw9sYj0SMFs1UgjbuL6pO5ueHh+qyumbtAq2K0Bci0kqOcU4E9fNtdiovQARAQAB +AA/+JHtlL39G1wsH9R6UEfUQJGXR9MiIiwZoKcnRB2o8+DS+OLjg0JOh8XehtuCs +E/8oGQKtQqa5bEIstX7IZoYmYFiUQi9LOzIblmp2vxOm+HKkxa4JszWci2/ZmC3t +KtaA4adl9XVnshoQ7pijuCMUKB3naBEOAxd8s9d/JeReGIYkJErdrnVfNk5N71Ds +FmH5Ll3XtEDvgBUQP3nkA6QFjpsaB94FHjL3gDwum/cxzj6pCglcvHOzEhfY0Ddb +J967FozQTaf2JW3O+w3LOqtcKWpq87B7+O61tVidQPSSuzPjCtFF0D2LC9R/Hpky +KTMQ6CaKja4MPhjwywd4QPcHGYSqjMpflvJqi+kYIt8psUK/YswWjnr3r4fbuqVY +VhtiHvnBHQjz135lUqWvEz4hM3Xpnxydx7aRlv5NlevK8+YIO5oFbWbGNTWsPZI5 +jpoFBpSsnR1Q5tnvtNHauvoWV+XN2qAOBTG+/nEbDYH6Ak3aaE9jrpTdYh0CotYF +q7csANsDy3JvkAzeU6WnYpsHHaAjqOGyiZGsLej1UcXPFMosE/aUo4WQhiS8Zx2c +zOVKOi/X5vQ2GdNT9Qolz8AriwzsvFR+bxPzyd8V6ALwDsoXvwEYinYBKK8j0OPv +OOihSR6HVsuP9NUZNU9ewiGzte/+/r6pNXHvR7wTQ8EWLcEIAN6Zyrb0bHZTIlxt +VWur/Ht2mIZrBaO50qmM5RD3T5oXzWXi/pjLrIpBMfeZR9DWfwQwjYzwqi7pxtYx +nJvbMuY505rfnMoYxb4J+cpRXV8MS7Dr1vjjLVUC9KiwSbM3gg6emfd2yuA93ihv +Pe3mffzLIiQa4mRE3wtGcioC43nWuV2K2e1KjxeFg07JhrezA/1Cak505ab/tmvP +4YmjR5c44+yL/YcQ3HdFgs4mV+nVbptRXvRcPpolJsgxPccGNdvHhsoR4gwXMS3F +RRPD2z6x8xeN73Q4KH3bm01swQdwFBZbWVfmUGLxvN7leCdfs9+iFJyqHiCIB6Iv +mQfp8F0IAOwSo8JhWN+V1dwML4EkIrM8wUb4yecNLkyR6TpPH/qXx4PxVMC+vy6x +sCtjeHIwKE+9vqnlhd5zOYh7qYXEJtYwdeDDmDbL8oks1LFfd+FyAuZXY33DLwn0 +cRYsr2OEZmaajqUB3NVmj3H4uJBN9+paFHyFSXrH68K1Fk2o3n+RSf2EiX+eICwI +L6rqoF5sSVUghBWdNegV7qfy4anwTQwrIMGjgU5S6PKW0Dr/3iO5z3qQpGPAj5OW +ATqPWkDICLbObPxD5cJlyyNE2wCA9VVc6/1d6w4EVwSq9h3/WTpATEreXXxTGptd +LNiTA1nmakBYNO2Iyo3djhaqBdWjk+EIAKtVEnJH9FAVwWOvaj1RoZMA5DnDMo7e +SnhrCXl8AL7Z1WInEaybasTJXn1uQ8xY52Ua4b8cbuEKRKzw/70NesFRoMLYoHTO +dyeszvhoDHberpGRTciVmpMu7Hyi33rM31K9epA4ib6QbbCHnxkWOZB+Bhgj1hJ8 +xb4RBYWiWpAYcg0+DAC3w9gfxQhtUlZPIbmbrBmrVkO2GVGUj8kH6k4UV6kUHEGY +HQWQR0HcbKcXW81ZXCCD0l7ROuEWQtTe5Jw7dJ4/QFuqZnPutXVRNOZqpl6eRShw +7X2/a29VXBpmHA95a88rSQsL+qm7Fb3prqRmuMCtrUZgFz7HLSTuUMR867QcTGVh +cCBUZXN0IEtleSA8bGVhcEBsZWFwLnNlPokCNwQTAQgAIQUCUL352QIbAwULCQgH +AwUVCgkICwUWAgMBAAIeAQIXgAAKCRAvRV4oJNGN30+xEACh9yLkZ4jqW0/wwyIM +MI896MQf1tAwzMj16MJYUjrjNK4Bn57QaQW926HsxF8C/OjT0MTRhq7heYZJnnEo +rj0rzpkJapUveTRkKeoTRtGGigqJYfkOTU7KRVwgJBXIfaKlI3tC3cX0j0H1fVKX +hLxsj5pNSPRCVf2A5mePg44HtXe6oVWSJ8+EcdTa0shf03NhAtFaY0BbFGPSm9mA +QUe4rxugwXPLctIyV4uweFo5BXFBCb4kKTBdnQi3aJwnoWLNT6rDdTe4/nhY0Hfo +alTCYGLkhio77gBHwpTOjEMO/hZhcDMi4CvxMPw7bRxAwq4u+0j0pDhkiLcQs4U4 +Ou/fH+pia+1nF5h19cNVXIm+RX2fL0wxVYc/14AIAK3YT6PVev9XYEkogSj0P7Kb +HKOruYpnToXJBERNJZwGL1U+ihPNUyroRf29t7u8flnXsOpCtBEIWAO8Muy5pWjV +3O6zAUCfWetAieCQ7WrQVmdJDa7dlX3Qx1XagUzqZdAq2jVI1hOWDA2rKytnReSF +/A97rmLaWZ8aoNCs8i4NLcy9Lbzi9QtornYGVCEmTTym0tM9L/mn7gAJ8dqUwt7n +s24dibfElky4ZZeItD+D7OZGeh0FDuejvv2dXFqL1/pkHpGBZhEckg0fZ95NbgMC +4pSZkZnqRpr2GwfB5aFfB6sIIJ0HGARQvfnZARAAtCP8Z9bm6KzIA7wbXx9LBIcJ +1wQvOPf99s4nOrnQev9xH5PZ820qS9xUjrlyE2bGYAhz5Cmq56ENs7THErIdZHtQ +uYEBprO+VFZjP50vtmCOL2PDl/xgv6J9r1Mp3KnR/m0esR+YceDW1qX07IkB5s+Q +us80v5LmmxnWcikWmR7dt1kOyV/+M6Y6mwvfQ4x3D/QUpO7SfMCOG5DGA7hVUHU/ +Tuh8MihmMFFOLAEEQI+1wkxr1W09HaYCcB+EZqxLSaBwMeFoYPJie9dBNBgps39o +6pDbjsO+or4JNuyoHvh8NNI5iY0IR2NMlW4mqCcHEazys2koxFTYK6YD95Vz0RkA +K4BErCDk3lVR0PH4StmLU3gmPayIjvi9Dl9saPRyu4Xx2WVi+q6spl3ckn4c4f3+ +iD8hxVp74+wa5ew0fIXjIpMoHCar/nndsse4i8glCddINdiOPPmhI9Wi3nT+5Z2t +9omPP2dEh0CzR+j1zvUpT3KtmhVICqhO+QP9BTJOwrp81NTlq9mbUyzTtVk/9dy3 +zoYbhKvY08k6LJ9FsQYySqtfJZ4cwl5WsOhALWwOwlMLA9wkz0eemgFxStyOylzl +QKoIK7zHuU6XYOXa32KSPIWaLy+WgIG/u2ObWtdE3CXVIUuSt5BQFnv7XVNHJllD +Az9VDEkOSYOiSEFVoUsAEQEAAQAP/1AagnZQZyzHDEgw4QELAspYHCWLXE5aZInX +wTUJhK31IgIXNn9bJ0hFiSpQR2xeMs9oYtRuPOu0P8oOFMn4/z374fkjZy8QVY3e +PlL+3EUeqYtkMwlGNmVw5a/NbNuNfm5Darb7pEfbYd1gPcni4MAYw7R2SG/57GbC +9gucvspHIfOSfBNLBthDzmK8xEKe1yD2eimfc2T7IRYb6hmkYfeds5GsqvGI6mwI +85h4uUHWRc5JOlhVM6yX8hSWx0L60Z3DZLChmc8maWnFXd7C8eQ6P1azJJbW71Ih +7CoK0XW4LE82vlQurSRFgTwfl7wFYszW2bOzCuhHDDtYnwH86Nsu0DC78ZVRnvxn +E8Ke/AJgrdhIOo4UAyR+aZD2+2mKd7/waOUTUrUtTzc7i8N3YXGi/EIaNReBXaq+ +ZNOp24BlFzRp+FCF/pptDW9HjPdiV09x0DgICmeZS4Gq/4vFFIahWctg52NGebT0 +Idxngjj+xDtLaZlLQoOz0n5ByjO/Wi0ANmMv1sMKCHhGvdaSws2/PbMR2r4caj8m +KXpIgdinM/wUzHJ5pZyF2U/qejsRj8Kw8KH/tfX4JCLhiaP/mgeTuWGDHeZQERAT +xPmRFHaLP9/ZhvGNh6okIYtrKjWTLGoXvKLHcrKNisBLSq+P2WeFrlme1vjvJMo/ +jPwLT5o9CADQmcbKZ+QQ1ZM9v99iDZol7SAMZX43JC019sx6GK0u6xouJBcLfeB4 +OXacTgmSYdTa9RM9fbfVpti01tJ84LV2SyL/VJq/enJF4XQPSynT/tFTn1PAor6o +tEAAd8fjKdJ6LnD5wb92SPHfQfXqI84rFEO8rUNIE/1ErT6DYifDzVCbfD2KZdoF +cOSp7TpD77sY1bs74ocBX5ejKtd+aH99D78bJSMM4pSDZsIEwnomkBHTziubPwJb +OwnATy0LmSMAWOw5rKbsh5nfwCiUTM20xp0t5JeXd+wPVWbpWqI2EnkCEN+RJr9i +7dp/ymDQ+Yt5wrsN3NwoyiexPOG91WQVCADdErHsnglVZZq9Z8Wx7KwecGCUurJ2 +H6lKudv5YOxPnAzqZS5HbpZd/nRTMZh2rdXCr5m2YOuewyYjvM757AkmUpM09zJX +MQ1S67/UX2y8/74TcRF97Ncx9HeELs92innBRXoFitnNguvcO6Esx4BTe1OdU6qR +ER3zAmVf22Le9ciXbu24DN4mleOH+OmBx7X2PqJSYW9GAMTsRB081R6EWKH7romQ +waxFrZ4DJzZ9ltyosEJn5F32StyLrFxpcrdLUoEaclZCv2qka7sZvi0EvovDVEBU +e10jOx9AOwf8Gj2ufhquQ6qgVYCzbP+YrodtkFrXRS3IsljIchj1M2ffB/0bfoUs +rtER9pLvYzCjBPg8IfGLw0o754Qbhh/ReplCRTusP/fQMybvCvfxreS3oyEriu/G +GufRomjewZ8EMHDIgUsLcYo2UHZsfF7tcazgxMGmMvazp4r8vpgrvW/8fIN/6Adu +tF+WjWDTvJLFJCe6O+BFJOWrssNrrra1zGtLC1s8s+Wfpe+bGPL5zpHeebGTwH1U +22eqgJArlEKxrfarz7W5+uHZJHSjF/K9ZvunLGD0n9GOPMpji3UO3zeM8IYoWn7E +/EWK1XbjnssNemeeTZ+sDh+qrD7BOi+vCX1IyBxbfqnQfJZvmcPWpruy1UsO+aIC +0GY8Jr3OL69dDQ21jueJAh8EGAEIAAkFAlC9+dkCGwwACgkQL0VeKCTRjd9HCw/+ +LQSVgLLF4ulYlPCjWIIuQwrPbJfWUVVr2dPUFVM85DCv8gBzk5c121snXh9Swovm +laBbw6ate3BmbXLh64jVE9Za5sbTWi7PCcbO/bpRy4d6oLmitmNw6cq0vjTLxUYy +bwuiJxWREkfxuU85EKdouN062YDevH+/YResmlJrcCE7LRlJFeRlKsrrwBU3BqYd +GgFJjKjQC1peeQ9fj62Y7xfwE9+PXbkiWO5u/Bk8hb1VZH1SoIRU98NHVcp6BVvp +VK0jLAXuSauSczULmpRjbyt1lhaAqivDTWEEZXiNNbRyp17c3nVdPWOcgBr42hdQ +z25CgZgyLCsvu82wuXLKJblrIPJX3Yf+si6KqEWBsmwdOWybsjygaF5HvzgFqAAD +U0goPWoQ71PorP2XOUNp5ZLkBQp5etvtkksjVNMIhnHn8PGMuoxO39EUGlWj2B5l +Cu8tSosAzB1pS8NcLZzoNoI9dOHrmgJmP+GrOUkcf5GhNZbMoj4GNfGBRYX0SZlQ +GuDrwNKYj73C4MWyNnnUFyq8nDHJ/G1NpaF2hiof9RBL4PUU/f92JkceXPBXA8gL +Mz2ig1OButwPPLFGQhWqxXAGrsS3Ny+BhTJfnfIbbkaLLphBpDZm1D9XKbAUvdd1 +RZXoH+FTg9UAW87eqU610npOkT6cRaBxaMK/mDtGNdc= +=JTFu +-----END PGP PRIVATE KEY BLOCK----- +""" -- cgit v1.2.3 From 32999ef8d08b6e94d356ea5fbce43ceebbf5247c Mon Sep 17 00:00:00 2001 From: drebs Date: Fri, 19 Apr 2013 12:47:22 -0300 Subject: Make the key wrapper map an object property. --- src/leap/common/keymanager/__init__.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/leap/common/keymanager/__init__.py b/src/leap/common/keymanager/__init__.py index 10acb36..8296b92 100644 --- a/src/leap/common/keymanager/__init__.py +++ b/src/leap/common/keymanager/__init__.py @@ -40,11 +40,6 @@ from leap.common.keymanager.openpgp import ( ) -wrapper_map = { - OpenPGPKey: OpenPGPWrapper(), -} - - class KeyManager(object): def __init__(self, address, url): @@ -59,6 +54,9 @@ class KeyManager(object): """ self.address = address self.url = url + self.wrapper_map = { + OpenPGPKey: OpenPGPWrapper(), + } def send_key(self, ktype, send_private=False, password=None): """ @@ -99,13 +97,13 @@ class KeyManager(object): keyserver. """ try: - return wrapper_map[ktype].get_key(address) + return self.wrapper_map[ktype].get_key(address) except KeyNotFound: key = filter(lambda k: isinstance(k, ktype), self._fetch_keys(address)) if key is None: raise KeyNotFound() - wrapper_map[ktype].put_key(key) + self.wrapper_map[ktype].put_key(key) return key @@ -137,4 +135,4 @@ class KeyManager(object): @return: The generated key. @rtype: EncryptionKey """ - return wrapper_map[ktype].gen_key(self.address) + return self.wrapper_map[ktype].gen_key(self.address) -- cgit v1.2.3 From b833d9042da3a1650fde3354f38998a2e497672b Mon Sep 17 00:00:00 2001 From: drebs Date: Fri, 19 Apr 2013 21:48:57 -0300 Subject: Make keymanager OpenPGP wrapper store using Soledad. --- src/leap/common/keymanager/__init__.py | 29 ++-- src/leap/common/keymanager/errors.py | 1 - src/leap/common/keymanager/gpg.py | 1 - src/leap/common/keymanager/keys.py | 25 ++- src/leap/common/keymanager/openpgp.py | 282 ++++++++++++++++++++++++++----- src/leap/common/tests/test_keymanager.py | 26 ++- 6 files changed, 289 insertions(+), 75 deletions(-) diff --git a/src/leap/common/keymanager/__init__.py b/src/leap/common/keymanager/__init__.py index 8296b92..d197e4c 100644 --- a/src/leap/common/keymanager/__init__.py +++ b/src/leap/common/keymanager/__init__.py @@ -21,12 +21,6 @@ Key Manager is a Nicknym agent for LEAP client. """ -try: - import simplejson as json -except ImportError: - import json # noqa - - from u1db.errors import HTTPError @@ -42,20 +36,22 @@ from leap.common.keymanager.openpgp import ( class KeyManager(object): - def __init__(self, address, url): + def __init__(self, address, url, soledad): """ Initialize a Key Manager for user's C{address} with provider's nickserver reachable in C{url}. @param address: The address of the user of this Key Manager. @type address: str - @param url: The URL of the key manager. + @param url: The URL of the nickserver. @type url: str + @param soledad: A Soledad instance for local storage of keys. + @type soledad: leap.soledad.Soledad """ - self.address = address - self.url = url - self.wrapper_map = { - OpenPGPKey: OpenPGPWrapper(), + self._address = address + self._url = url + self._wrapper_map = { + OpenPGPKey: OpenPGPWrapper(soledad), } def send_key(self, ktype, send_private=False, password=None): @@ -97,16 +93,15 @@ class KeyManager(object): keyserver. """ try: - return self.wrapper_map[ktype].get_key(address) + return self._wrapper_map[ktype].get_key(address) except KeyNotFound: key = filter(lambda k: isinstance(k, ktype), self._fetch_keys(address)) if key is None: raise KeyNotFound() - self.wrapper_map[ktype].put_key(key) + self._wrapper_map[ktype].put_key(key) return key - def _fetch_keys(self, address): """ Fetch keys bound to C{address} from nickserver. @@ -119,11 +114,13 @@ class KeyManager(object): @raise KeyNotFound: If the key was not found on nickserver. @raise httplib.HTTPException: """ + raise NotImplementedError(self._fetch_keys) def refresh_keys(self): """ Update the user's db of validated keys to see if there are changes. """ + raise NotImplementedError(self.refresh_keys) def gen_key(self, ktype): """ @@ -135,4 +132,4 @@ class KeyManager(object): @return: The generated key. @rtype: EncryptionKey """ - return self.wrapper_map[ktype].gen_key(self.address) + return self._wrapper_map[ktype].gen_key(self._address) diff --git a/src/leap/common/keymanager/errors.py b/src/leap/common/keymanager/errors.py index f5bb1ab..4853869 100644 --- a/src/leap/common/keymanager/errors.py +++ b/src/leap/common/keymanager/errors.py @@ -16,7 +16,6 @@ # along with this program. If not, see . - class KeyNotFound(Exception): """ Raised when key was no found on keyserver. diff --git a/src/leap/common/keymanager/gpg.py b/src/leap/common/keymanager/gpg.py index dc5d791..5571ace 100644 --- a/src/leap/common/keymanager/gpg.py +++ b/src/leap/common/keymanager/gpg.py @@ -395,4 +395,3 @@ class GPGWrapper(gnupg.GPG): @rtype: bool """ self.is_encrypted_asym() or self.is_encrypted_sym() - diff --git a/src/leap/common/keymanager/keys.py b/src/leap/common/keymanager/keys.py index 13e3c0b..2e1ed89 100644 --- a/src/leap/common/keymanager/keys.py +++ b/src/leap/common/keymanager/keys.py @@ -21,6 +21,12 @@ Abstact key type and wrapper representations. """ +try: + import simplejson as json +except ImportError: + import json # noqa + + from abc import ABCMeta, abstractmethod @@ -44,13 +50,13 @@ class EncryptionKey(object): __metaclass__ = ABCMeta def __init__(self, address, key_id=None, fingerprint=None, - key_data=None, length=None, expiry_date=None, - validation=None, first_seen_at=None, - last_audited_at=None): + key_data=None, private=None, length=None, expiry_date=None, + validation=None, first_seen_at=None, last_audited_at=None): self.address = address self.key_id = key_id self.fingerprint = fingerprint self.key_data = key_data + self.private = private self.length = length self.expiry_date = expiry_date self.validation = validation @@ -66,10 +72,11 @@ class EncryptionKey(object): """ return json.dumps({ 'address': self.address, - 'type': str(self.__type__), + 'type': str(self.__class__), 'key_id': self.key_id, 'fingerprint': self.fingerprint, 'key_data': self.key_data, + 'private': self.private, 'length': self.length, 'expiry_date': self.expiry_date, 'validation': self.validation, @@ -92,6 +99,15 @@ class KeyTypeWrapper(object): __metaclass__ = ABCMeta + def __init__(self, soledad): + """ + Initialize the Key Type Wrapper. + + @param soledad: A Soledad instance for local storage of keys. + @type soledad: leap.soledad.Soledad + """ + self._soledad = soledad + @abstractmethod def get_key(self, address): """ @@ -124,4 +140,3 @@ class KeyTypeWrapper(object): @return: The key bound to C{address}. @rtype: EncryptionKey """ - diff --git a/src/leap/common/keymanager/openpgp.py b/src/leap/common/keymanager/openpgp.py index bb73089..1c51d94 100644 --- a/src/leap/common/keymanager/openpgp.py +++ b/src/leap/common/keymanager/openpgp.py @@ -22,7 +22,10 @@ Infrastructure for using OpenPGP keys in Key Manager. import re +import tempfile +import shutil +from hashlib import sha256 from leap.common.keymanager.errors import ( KeyNotFound, KeyAlreadyExists, @@ -34,6 +37,153 @@ from leap.common.keymanager.keys import ( from leap.common.keymanager.gpg import GPGWrapper +# +# Utility functions +# + +def _is_address(address): + """ + Return whether the given C{address} is in the form user@provider. + + @param address: The address to be tested. + @type address: str + @return: Whether C{address} is in the form user@provider. + @rtype: bool + """ + return bool(re.match('[\w.-]+@[\w.-]+', address)) + + +def _build_key_from_doc(address, doc): + """ + Build an OpenPGPKey for C{address} based on C{doc} from local storage. + + @param address: The address bound to the key. + @type address: str + @param doc: Document obtained from Soledad storage. + @type doc: leap.soledad.backends.leap_backend.LeapDocument + @return: The built key. + @rtype: OpenPGPKey + """ + return OpenPGPKey( + address, + key_id=doc.content['key_id'], + fingerprint=doc.content['fingerprint'], + key_data=doc.content['key_data'], + private=doc.content['private'], + length=doc.content['length'], + expiry_date=doc.content['expiry_date'], + validation=None, # TODO: verify for validation. + ) + + +def _build_key_from_gpg(address, key, key_data): + """ + Build an OpenPGPKey for C{address} based on C{key} from + local gpg storage. + + ASCII armored GPG key data has to be queried independently in this + wrapper, so we receive it in C{key_data}. + + @param address: The address bound to the key. + @type address: str + @param key: Key obtained from GPG storage. + @type key: dict + @param key_data: Key data obtained from GPG storage. + @type key_data: str + @return: The built key. + @rtype: OpenPGPKey + """ + return OpenPGPKey( + address, + key_id=key['keyid'], + fingerprint=key['fingerprint'], + key_data=key_data, + private=True if key['type'] == 'sec' else False, + length=key['length'], + expiry_date=key['expires'], + validation=None, # TODO: verify for validation. + ) + + +def _keymanager_doc_id(address, private=False): + """ + Return the document id for the document containing a key for + C{address}. + + @param address: The address bound to the key. + @type address: str + @param private: Whether the key is private or not. + @type private: bool + @return: The document id for the document that stores a key bound to + C{address}. + @rtype: str + """ + assert _is_address(address) + ktype = 'private' if private else 'public' + return sha256('key-manager-'+address+'-'+ktype).hexdigest() + + +def _build_unitary_gpgwrapper(key_data=None): + """ + Return a temporary GPG wrapper keyring containing exactly zero or one + keys. + + Temporary unitary keyrings allow the to use GPG's facilities for exactly + one key. This function creates an empty temporary keyring and imports + C{key_data} if it is not None. + + @param key_data: ASCII armored key data. + @type key_data: str + @return: A GPG wrapper with a unitary keyring. + @rtype: gnupg.GPG + """ + tmpdir = tempfile.mkdtemp() + gpg = GPGWrapper(gnupghome=tmpdir) + assert len(gpg.list_keys()) is 0 + if key_data: + gpg.import_keys(key_data) + assert len(gpg.list_keys()) is 1 + return gpg + + +def _destroy_unitary_gpgwrapper(gpg): + """ + Securely erase a unitary keyring. + + @param gpg: A GPG wrapper instance. + @type gpg: gnupg.GPG + """ + for secret in [True, False]: + for key in gpg.list_keys(secret=secret): + gpg.delete_keys( + key['fingerprint'], + secret=secret) + assert len(gpg.list_keys()) == 0 + # TODO: implement some kind of wiping of data or a more secure way that + # does not write to disk. + shutil.rmtree(gpg.gnupghome) + + +def _safe_call(callback, key_data=None, **kwargs): + """ + Run C{callback} in an unitary keyring containing C{key_data}. + + @param callback: Function whose first argument is the gpg keyring. + @type callback: function(gnupg.GPG) + @param key_data: ASCII armored key data. + @type key_data: str + @param **kwargs: Other eventual parameters for the callback. + @type **kwargs: **dict + """ + gpg = _build_unitary_gpgwrapper(key_data) + callback(gpg, **kwargs) + _destroy_unitary_gpgwrapper(gpg) + + +# +# The OpenPGP wrapper +# + class OpenPGPKey(EncryptionKey): """ Base class for OpenPGP keys. @@ -45,33 +195,19 @@ class OpenPGPWrapper(KeyTypeWrapper): A wrapper for OpenPGP keys. """ - def __init__(self, gnupghome=None): - self._gpg = GPGWrapper(gnupghome=gnupghome) - - def _build_key(self, address, result): + def __init__(self, soledad): """ - Build an OpenPGPWrapper key for C{address} based on C{result} from - local storage. + Initialize the OpenPGP wrapper. - @param address: The address bound to the key. - @type address: str - @param result: Result obtained from GPG storage. - @type result: dict + @param soledad: A Soledad instance for key storage. + @type soledad: leap.soledad.Soledad """ - key_data = self._gpg.export_keys(result['fingerprint'], secret=False) - return OpenPGPKey( - address, - key_id=result['keyid'], - fingerprint=result['fingerprint'], - key_data=key_data, - length=result['length'], - expiry_date=result['expires'], - validation=None, # TODO: verify for validation. - ) + KeyTypeWrapper.__init__(self, soledad) + self._soledad = soledad def gen_key(self, address): """ - Generate an OpenPGP keypair for C{address}. + Generate an OpenPGP keypair bound to C{address}. @param address: The address bound to the key. @type address: str @@ -79,21 +215,36 @@ class OpenPGPWrapper(KeyTypeWrapper): @rtype: OpenPGPKey @raise KeyAlreadyExists: If key already exists in local database. """ + # make sure the key does not already exist + assert _is_address(address) try: self.get_key(address) - raise KeyAlreadyExists() + raise KeyAlreadyExists(address) except KeyNotFound: pass - params = self._gpg.gen_key_input( - key_type='RSA', - key_length=4096, - name_real=address, - name_email=address, - name_comment='Generated by LEAP Key Manager.') - self._gpg.gen_key(params) - return self.get_key(address) - - def get_key(self, address): + + def _gen_key_cb(gpg): + params = gpg.gen_key_input( + key_type='RSA', + key_length=4096, + name_real=address, + name_email=address, + name_comment='Generated by LEAP Key Manager.') + gpg.gen_key(params) + assert len(gpg.list_keys()) is 1 # a unitary keyring! + key = gpg.list_keys(secret=True).pop() + assert len(key['uids']) is 1 # with just one uid! + # assert for correct address + assert re.match('.*<%s>$' % address, key['uids'][0]) is not None + openpgp_key = _build_key_from_gpg( + address, key, + gpg.export_keys(key['fingerprint'])) + self.put_key(openpgp_key) + + _safe_call(_gen_key_cb) + return self.get_key(address, private=True) + + def get_key(self, address, private=False): """ Get key bound to C{address} from local storage. @@ -104,23 +255,62 @@ class OpenPGPWrapper(KeyTypeWrapper): @rtype: OpenPGPKey @raise KeyNotFound: If the key was not found on local storage. """ - m = re.compile('.*<%s>$' % address) - keys = self._gpg.list_keys(secret=False) + assert _is_address(address) + doc = self._get_key_doc(address, private) + if doc is None: + raise KeyNotFound(address) + return _build_key_from_doc(address, doc) - def bound_to_address(key): - return bool(filter(lambda u: m.match(u), key['uids'])) + def put_key_raw(self, data): + """ + Put key contained in raw C{data} in local storage. - try: - bound_key = filter(bound_to_address, keys).pop() - return self._build_key(address, bound_key) - except IndexError: - raise KeyNotFound(address) + @param data: The key data to be stored. + @type data: str + """ + assert data is not None + + def _put_key_raw_cb(gpg): + + key = gpg.list_keys(secret=False).pop() # unitary keyring + # extract adress from first uid on key + match = re.match('.*<([\w.-]+@[\w.-]+)>.*', key['uids'].pop()) + assert match is not None + address = match.group(1) + openpgp_key = _build_key_from_gpg( + address, key, + gpg.export_keys(key['fingerprint'])) + self.put_key(openpgp_key) - def put_key(self, data): + _safe_call(_put_key_raw_cb, data) + + def put_key(self, key): + """ + Put C{key} in local storage. + + @param key: The key to be stored. + @type key: OpenPGPKey + """ + doc = self._get_key_doc(key.address, private=key.private) + if doc is None: + self._soledad.create_doc_from_json( + key.get_json(), + doc_id=_keymanager_doc_id(key.address, key.private)) + else: + doc.set_json(key.get_json()) + self._soledad.put_doc(doc) + + def _get_key_doc(self, address, private=False): """ - Put key contained in {data} in local storage. + Get the document with a key (public, by default) bound to C{address}. + + If C{private} is True, looks for a private key instead of a public. - @param key: The key data to be stored. - @type key: str + @param address: The address bound to the key. + @type address: str + @param private: Whether to look for a private key. + @type private: bool + @return: The document with the key or None if it does not exist. + @rtype: leap.soledad.backends.leap_backend.LeapDocument """ - self._gpg.import_keys(data) + return self._soledad.get_doc(_keymanager_doc_id(address, private)) diff --git a/src/leap/common/tests/test_keymanager.py b/src/leap/common/tests/test_keymanager.py index 4189aac..23d702b 100644 --- a/src/leap/common/tests/test_keymanager.py +++ b/src/leap/common/tests/test_keymanager.py @@ -26,12 +26,26 @@ import unittest from leap.common.testing.basetest import BaseLeapTest from leap.common.keymanager import KeyManager, openpgp, KeyNotFound - +from leap.soledad import Soledad +from leap.common.keymanager.gpg import GPGWrapper class KeyManagerTestCase(BaseLeapTest): def setUp(self): - pass + self._soledad = Soledad( + "leap@leap.se", + "123456", + gnupg_home=self.tempdir+"/gnupg", + secret_path=self.tempdir+"/secret.gpg", + local_db_path=self.tempdir+"/soledad.u1db", + bootstrap=False, + ) + # initialize solead by hand for testing purposes + self._soledad._init_dirs() + self._soledad._gpg = GPGWrapper(gnupghome=self.tempdir+"/gnupg") + self._soledad._shared_db = None + self._soledad._init_keys() + self._soledad._init_db() def tearDown(self): pass @@ -40,7 +54,7 @@ class KeyManagerTestCase(BaseLeapTest): return KeyManager(user, url) def test_openpgp_gen_key(self): - pgp = openpgp.OpenPGPWrapper(self.tempdir+'/gnupg') + pgp = openpgp.OpenPGPWrapper(self._soledad) try: pgp.get_key('user@leap.se') except KeyNotFound: @@ -51,12 +65,12 @@ class KeyManagerTestCase(BaseLeapTest): self.assertEqual( '4096', key.length, 'Wrong key length.') - def test_openpgp_put_key(self): - pgp = openpgp.OpenPGPWrapper(self.tempdir+'/gnupg2') + def test_openpgp_put_key_raw(self): + pgp = openpgp.OpenPGPWrapper(self._soledad) try: pgp.get_key('leap@leap.se') except KeyNotFound: - pgp.put_key(PUBLIC_KEY) + pgp.put_key_raw(PUBLIC_KEY) key = pgp.get_key('leap@leap.se') self.assertIsInstance(key, openpgp.OpenPGPKey) self.assertEqual( -- cgit v1.2.3 From b3ad976ec8aa64a00cc824dc57aa2135ab41deb6 Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 22 Apr 2013 10:39:58 -0300 Subject: Add send_keys() and refresh_keys() to Key Manager. --- src/leap/common/keymanager/__init__.py | 54 +++++++++++++-- src/leap/common/keymanager/errors.py | 5 ++ src/leap/common/keymanager/http.py | 78 +++++++++++++++++++++ src/leap/common/keymanager/keys.py | 4 +- src/leap/common/keymanager/openpgp.py | 112 ++++++++++++++----------------- src/leap/common/keymanager/util.py | 97 ++++++++++++++++++++++++++ src/leap/common/tests/test_keymanager.py | 81 +++++++++++++++++++++- 7 files changed, 362 insertions(+), 69 deletions(-) create mode 100644 src/leap/common/keymanager/http.py create mode 100644 src/leap/common/keymanager/util.py diff --git a/src/leap/common/keymanager/__init__.py b/src/leap/common/keymanager/__init__.py index d197e4c..a195724 100644 --- a/src/leap/common/keymanager/__init__.py +++ b/src/leap/common/keymanager/__init__.py @@ -20,10 +20,13 @@ Key Manager is a Nicknym agent for LEAP client. """ +import httplib + from u1db.errors import HTTPError +from leap.common.check import leap_assert from leap.common.keymanager.errors import ( KeyNotFound, KeyAlreadyExists, @@ -31,7 +34,9 @@ from leap.common.keymanager.errors import ( from leap.common.keymanager.openpgp import ( OpenPGPKey, OpenPGPWrapper, + _encrypt_symmetric, ) +from leap.common.keymanager.http import HTTPClient class KeyManager(object): @@ -49,9 +54,10 @@ class KeyManager(object): @type soledad: leap.soledad.Soledad """ self._address = address - self._url = url + self._http_client = HTTPClient(url) self._wrapper_map = { OpenPGPKey: OpenPGPWrapper(soledad), + # other types of key will be added to this mapper. } def send_key(self, ktype, send_private=False, password=None): @@ -73,9 +79,32 @@ class KeyManager(object): @type ktype: KeyType @raise httplib.HTTPException: + @raise KeyNotFound: If the key was not found both locally and in + keyserver. """ - - def get_key(self, address, ktype): + # prepare the public key bound to address + data = { + 'address': self._address, + 'keys': [ + json.loads( + self.get_key( + self._address, ktype, private=False).get_json()), + ] + } + # prepare the private key bound to address + if send_private: + privkey = json.loads( + self.get_key(self._address, ktype, private=True).get_json()) + privkey.key_data = _encrypt_symmetric(data, passphrase) + data['keys'].append(privkey) + headers = None # TODO: replace for token-based-auth + self._http_client.request( + 'PUT', + '/key/%s' % address, + json.dumps(data), + headers) + + def get_key(self, address, ktype, private=False): """ Return a key of type C{ktype} bound to C{address}. @@ -86,14 +115,19 @@ class KeyManager(object): @type address: str @param ktype: The type of the key. @type ktype: KeyType + @param private: Look for a private key instead of a public one? + @type private: bool @return: A key of type C{ktype} bound to C{address}. @rtype: EncryptionKey @raise KeyNotFound: If the key was not found both locally and in keyserver. """ + leap_assert( + ktype in self._wrapper_map, + 'Unkown key type: %s.' % str(ktype)) try: - return self._wrapper_map[ktype].get_key(address) + return self._wrapper_map[ktype].get_key(address, private=private) except KeyNotFound: key = filter(lambda k: isinstance(k, ktype), self._fetch_keys(address)) @@ -114,7 +148,17 @@ class KeyManager(object): @raise KeyNotFound: If the key was not found on nickserver. @raise httplib.HTTPException: """ - raise NotImplementedError(self._fetch_keys) + self._http_client.request('GET', '/key/%s' % address, None, None) + keydata = json.loads(self._http_client.read_response()) + leap_assert( + keydata['address'] == address, + "Fetched key for wrong address.") + for key in keydata['keys']: + # find the key class in the mapper + keyCLass = filter( + lambda klass: str(klass) == key['type'], + self._wrapper_map).pop() + yield _build_key_from_dict(kClass, address, key) def refresh_keys(self): """ diff --git a/src/leap/common/keymanager/errors.py b/src/leap/common/keymanager/errors.py index 4853869..886c666 100644 --- a/src/leap/common/keymanager/errors.py +++ b/src/leap/common/keymanager/errors.py @@ -16,6 +16,11 @@ # along with this program. If not, see . +""" +Errors and exceptions used by the Key Manager. +""" + + class KeyNotFound(Exception): """ Raised when key was no found on keyserver. diff --git a/src/leap/common/keymanager/http.py b/src/leap/common/keymanager/http.py new file mode 100644 index 0000000..478137d --- /dev/null +++ b/src/leap/common/keymanager/http.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +# http.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +""" +HTTP utilities. +""" + + +import urlparse +import httplib + + +def HTTPClient(object): + """ + A simple HTTP client for making requests. + """ + + def __init__(self, url): + """ + Initialize the HTTP client. + """ + self._url = urlparse.urlsplit(url) + self._conn = None + + def _ensure_connection(self): + """ + Ensure the creation of the connection object. + """ + if self._conn is not None: + return + if self._url.scheme == 'https': + connClass = httplib.HTTPSConnection + else: + connClass = httplib.HTTPConnection + self._conn = connClass(self._url.hostname, self._url.port) + + def request(method, url_query, body, headers): + """ + Make an HTTP request. + + @param method: The method of the request. + @type method: str + @param url_query: The URL query string of the request. + @type url_query: str + @param body: The body of the request. + @type body: str + @param headers: Headers to be sent on the request. + @type headers: list of str + """ + self._ensure_connection() + return self._conn.request(mthod, url_query, body, headers) + + def response(self): + """ + Return the response of an HTTP request. + """ + return self._conn.getresponse() + + def read_response(self): + """ + Get the contents of a response for an HTTP request. + """ + return self.response().read() diff --git a/src/leap/common/keymanager/keys.py b/src/leap/common/keymanager/keys.py index 2e1ed89..bed407c 100644 --- a/src/leap/common/keymanager/keys.py +++ b/src/leap/common/keymanager/keys.py @@ -109,12 +109,14 @@ class KeyTypeWrapper(object): self._soledad = soledad @abstractmethod - def get_key(self, address): + def get_key(self, address, private=False): """ Get key from local storage. @param address: The address bound to the key. @type address: str + @param private: Look for a private key instead of a public one? + @type private: bool @return: The key bound to C{address}. @rtype: EncryptionKey diff --git a/src/leap/common/keymanager/openpgp.py b/src/leap/common/keymanager/openpgp.py index 1c51d94..cd37138 100644 --- a/src/leap/common/keymanager/openpgp.py +++ b/src/leap/common/keymanager/openpgp.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# openpgpwrapper.py +# openpgp.py # Copyright (C) 2013 LEAP # # This program is free software: you can redistribute it and/or modify @@ -25,7 +25,7 @@ import re import tempfile import shutil -from hashlib import sha256 +from leap.common.check import leap_assert from leap.common.keymanager.errors import ( KeyNotFound, KeyAlreadyExists, @@ -35,45 +35,40 @@ from leap.common.keymanager.keys import ( KeyTypeWrapper, ) from leap.common.keymanager.gpg import GPGWrapper +from leap.common.keymanager.util import ( + _is_address, + _build_key_from_doc, + _keymanager_doc_id, +) # # Utility functions # -def _is_address(address): - """ - Return whether the given C{address} is in the form user@provider. - - @param address: The address to be tested. - @type address: str - @return: Whether C{address} is in the form user@provider. - @rtype: bool +def _encrypt_symmetric(data, password): """ - return bool(re.match('[\w.-]+@[\w.-]+', address)) + Encrypt C{data} with C{password}. + This function uses the OpenPGP wrapper to perform the encryption. -def _build_key_from_doc(address, doc): + @param data: The data to be encrypted. + @type data: str + @param password: The password used to encrypt C{data}. + @type password: str + @return: The encrypted data. + @rtype: str """ - Build an OpenPGPKey for C{address} based on C{doc} from local storage. + cyphertext = None - @param address: The address bound to the key. - @type address: str - @param doc: Document obtained from Soledad storage. - @type doc: leap.soledad.backends.leap_backend.LeapDocument - @return: The built key. - @rtype: OpenPGPKey - """ - return OpenPGPKey( - address, - key_id=doc.content['key_id'], - fingerprint=doc.content['fingerprint'], - key_data=doc.content['key_data'], - private=doc.content['private'], - length=doc.content['length'], - expiry_date=doc.content['expiry_date'], - validation=None, # TODO: verify for validation. - ) + def _encrypt_cb(gpg): + cyphertext = str( + gpg.encrypt( + data, None, passphrase=password, symmetric=True)) + data['keys'].append(privkey) + + _safe_call(_encrypt_cb) + return cyphertext def _build_key_from_gpg(address, key, key_data): @@ -90,7 +85,7 @@ def _build_key_from_gpg(address, key, key_data): @type key: dict @param key_data: Key data obtained from GPG storage. @type key_data: str - @return: The built key. + @return: An instance of the key. @rtype: OpenPGPKey """ return OpenPGPKey( @@ -105,24 +100,6 @@ def _build_key_from_gpg(address, key, key_data): ) -def _keymanager_doc_id(address, private=False): - """ - Return the document id for the document containing a key for - C{address}. - - @param address: The address bound to the key. - @type address: str - @param private: Whether the key is private or not. - @type private: bool - @return: The document id for the document that stores a key bound to - C{address}. - @rtype: str - """ - assert _is_address(address) - ktype = 'private' if private else 'public' - return sha256('key-manager-'+address+'-'+ktype).hexdigest() - - def _build_unitary_gpgwrapper(key_data=None): """ Return a temporary GPG wrapper keyring containing exactly zero or one @@ -139,10 +116,13 @@ def _build_unitary_gpgwrapper(key_data=None): """ tmpdir = tempfile.mkdtemp() gpg = GPGWrapper(gnupghome=tmpdir) - assert len(gpg.list_keys()) is 0 + leap_assert(len(gpg.list_keys()) is 0, 'Keyring not empty.') if key_data: gpg.import_keys(key_data) - assert len(gpg.list_keys()) is 1 + leap_assert( + len(gpg.list_keys()) is 1, + 'Unitary keyring has wrong number of keys: %d.' + % len(gpg.list_keys())) return gpg @@ -158,7 +138,7 @@ def _destroy_unitary_gpgwrapper(gpg): gpg.delete_keys( key['fingerprint'], secret=secret) - assert len(gpg.list_keys()) == 0 + leap_assert(len(gpg.list_keys()) is 0, 'Keyring not empty!') # TODO: implement some kind of wiping of data or a more secure way that # does not write to disk. shutil.rmtree(gpg.gnupghome) @@ -216,7 +196,7 @@ class OpenPGPWrapper(KeyTypeWrapper): @raise KeyAlreadyExists: If key already exists in local database. """ # make sure the key does not already exist - assert _is_address(address) + leap_assert(_is_address(address), 'Not an user address: %s' % address) try: self.get_key(address) raise KeyAlreadyExists(address) @@ -231,11 +211,18 @@ class OpenPGPWrapper(KeyTypeWrapper): name_email=address, name_comment='Generated by LEAP Key Manager.') gpg.gen_key(params) - assert len(gpg.list_keys()) is 1 # a unitary keyring! + pubkeys = gpg.list_keys() + # assert for new key characteristics + leap_assert( + len(pubkeys) is 1, # a unitary keyring! + 'Keyring has wrong number of keys: %d.' % len(pubkeys)) key = gpg.list_keys(secret=True).pop() - assert len(key['uids']) is 1 # with just one uid! - # assert for correct address - assert re.match('.*<%s>$' % address, key['uids'][0]) is not None + leap_assert( + len(key['uids']) is 1, # with just one uid! + 'Wrong number of uids for key: %d.' % len(key['uids'])) + leap_assert( + re.match('.*<%s>$' % address, key['uids'][0]) is not None, + 'Key not correctly bound to address.') openpgp_key = _build_key_from_gpg( address, key, gpg.export_keys(key['fingerprint'])) @@ -250,16 +237,18 @@ class OpenPGPWrapper(KeyTypeWrapper): @param address: The address bound to the key. @type address: str + @param private: Look for a private key instead of a public one? + @type private: bool @return: The key bound to C{address}. @rtype: OpenPGPKey @raise KeyNotFound: If the key was not found on local storage. """ - assert _is_address(address) + leap_assert(_is_address(address), 'Not an user address: %s' % address) doc = self._get_key_doc(address, private) if doc is None: raise KeyNotFound(address) - return _build_key_from_doc(address, doc) + return _build_key_from_doc(OpenPGPKey, address, doc) def put_key_raw(self, data): """ @@ -268,14 +257,15 @@ class OpenPGPWrapper(KeyTypeWrapper): @param data: The key data to be stored. @type data: str """ - assert data is not None + # TODO: add more checks for correct key data. + leap_assert(data is not None, 'Data does not represent a key.') def _put_key_raw_cb(gpg): key = gpg.list_keys(secret=False).pop() # unitary keyring # extract adress from first uid on key match = re.match('.*<([\w.-]+@[\w.-]+)>.*', key['uids'].pop()) - assert match is not None + leap_assert(match is not None, 'No user address in key data.') address = match.group(1) openpgp_key = _build_key_from_gpg( address, key, diff --git a/src/leap/common/keymanager/util.py b/src/leap/common/keymanager/util.py new file mode 100644 index 0000000..42168c8 --- /dev/null +++ b/src/leap/common/keymanager/util.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +# util.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +""" +Utilities for the Key Manager. +""" + + +import re + + +from hashlib import sha256 +from leap.common.check import leap_assert + + +def _is_address(address): + """ + Return whether the given C{address} is in the form user@provider. + + @param address: The address to be tested. + @type address: str + @return: Whether C{address} is in the form user@provider. + @rtype: bool + """ + return bool(re.match('[\w.-]+@[\w.-]+', address)) + + +def _build_key_from_dict(kClass, address, kdict): + """ + Build an C{kClass} key bound to C{address} based on info in C{kdict}. + + @param address: The address bound to the key. + @type address: str + @param kdict: Dictionary with key data. + @type kdict: dict + @return: An instance of the key. + @rtype: C{kClass} + """ + return kClass( + address, + key_id=kdict['key_id'], + fingerprint=kdict['fingerprint'], + key_data=kdict['key_data'], + private=kdict['private'], + length=kdict['length'], + expiry_date=kdict['expiry_date'], + first_seen_at=kdict['first_seen_at'], + last_audited_at=kdict['last_audited_at'], + validation=kdict['validation'], # TODO: verify for validation. + ) + + +def _build_key_from_doc(kClass, address, doc): + """ + Build an C{kClass} for C{address} based on C{doc} from local storage. + + @param address: The address bound to the key. + @type address: str + @param doc: Document obtained from Soledad storage. + @type doc: leap.soledad.backends.leap_backend.LeapDocument + @return: An instance of the key. + @rtype: C{kClass} + """ + return _build_key_from_dict(kClass, address, doc.content) + + +def _keymanager_doc_id(address, private=False): + """ + Return the document id for the document containing a key for + C{address}. + + @param address: The address bound to the key. + @type address: str + @param private: Whether the key is private or not. + @type private: bool + @return: The document id for the document that stores a key bound to + C{address}. + @rtype: str + """ + leap_assert(_is_address(address), "Wrong address format: %s" % address) + ktype = 'private' if private else 'public' + return sha256('key-manager-'+address+'-'+ktype).hexdigest() diff --git a/src/leap/common/tests/test_keymanager.py b/src/leap/common/tests/test_keymanager.py index 23d702b..4a2693e 100644 --- a/src/leap/common/tests/test_keymanager.py +++ b/src/leap/common/tests/test_keymanager.py @@ -25,11 +25,88 @@ import unittest from leap.common.testing.basetest import BaseLeapTest -from leap.common.keymanager import KeyManager, openpgp, KeyNotFound from leap.soledad import Soledad +from leap.common.keymanager import KeyManager, openpgp, KeyNotFound +from leap.common.keymanager.openpgp import OpenPGPKey from leap.common.keymanager.gpg import GPGWrapper +from leap.common.keymanager.util import ( + _is_address, + _build_key_from_dict, + _keymanager_doc_id, +) + + +class KeyManagerUtilTestCase(BaseLeapTest): + + def setUp(self): + pass + + def tearDown(self): + pass + + def test__is_address(self): + self.assertTrue( + _is_address('user@leap.se'), + 'Incorrect address detection.') + self.assertFalse( + _is_address('userleap.se'), + 'Incorrect address detection.') + self.assertFalse( + _is_address('user@'), + 'Incorrect address detection.') + self.assertFalse( + _is_address('@leap.se'), + 'Incorrect address detection.') + + def test__build_key_from_dict(self): + kdict = { + 'address': 'leap@leap.se', + 'key_id': 'key_id', + 'fingerprint': 'fingerprint', + 'key_data': 'key_data', + 'private': 'private', + 'length': 'length', + 'expiry_date': 'expiry_date', + 'first_seen_at': 'first_seen_at', + 'last_audited_at': 'last_audited_at', + 'validation': 'validation', + } + key = _build_key_from_dict(OpenPGPKey, 'leap@leap.se', kdict) + self.assertEqual(kdict['address'], key.address, + 'Wrong data in key.') + self.assertEqual(kdict['key_id'], key.key_id, + 'Wrong data in key.') + self.assertEqual(kdict['fingerprint'], key.fingerprint, + 'Wrong data in key.') + self.assertEqual(kdict['key_data'], key.key_data, + 'Wrong data in key.') + self.assertEqual(kdict['private'], key.private, + 'Wrong data in key.') + self.assertEqual(kdict['length'], key.length, + 'Wrong data in key.') + self.assertEqual(kdict['expiry_date'], key.expiry_date, + 'Wrong data in key.') + self.assertEqual(kdict['first_seen_at'], key.first_seen_at, + 'Wrong data in key.') + self.assertEqual(kdict['last_audited_at'], key.last_audited_at, + 'Wrong data in key.') + self.assertEqual(kdict['validation'], key.validation, + 'Wrong data in key.') + + def test__keymanager_doc_id(self): + doc_id1 = _keymanager_doc_id('leap@leap.se', private=False) + doc_id2 = _keymanager_doc_id('leap@leap.se', private=True) + doc_id3 = _keymanager_doc_id('user@leap.se', private=False) + doc_id4 = _keymanager_doc_id('user@leap.se', private=True) + self.assertFalse(doc_id1 == doc_id2, 'Doc ids are equal!') + self.assertFalse(doc_id1 == doc_id3, 'Doc ids are equal!') + self.assertFalse(doc_id1 == doc_id4, 'Doc ids are equal!') + self.assertFalse(doc_id2 == doc_id3, 'Doc ids are equal!') + self.assertFalse(doc_id2 == doc_id4, 'Doc ids are equal!') + self.assertFalse(doc_id3 == doc_id4, 'Doc ids are equal!') + -class KeyManagerTestCase(BaseLeapTest): +class KeyManagerCryptoTestCase(BaseLeapTest): def setUp(self): self._soledad = Soledad( -- cgit v1.2.3 From 62b5a7798924188ba915a1c095917d8709e20ae7 Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 23 Apr 2013 20:50:02 -0300 Subject: Refactor, fixes, add api, tests. * Change KeyTypeWrapper to EncryptionScheme * Change OpenPGPWrapper to OpenPGPScheme * Add missing and standardized crypto API. * Add delete_key() * Fix put_key raw so it puts either public or private keys. * Fix gpg's is_encrypted() * Fix openpgp's safe callbacks so they return correctly. * Remove binascii because it generates invalid doc ids. * Add tests. --- src/leap/common/keymanager/__init__.py | 8 +- src/leap/common/keymanager/errors.py | 7 + src/leap/common/keymanager/gpg.py | 2 +- src/leap/common/keymanager/keys.py | 25 +++- src/leap/common/keymanager/openpgp.py | 212 +++++++++++++++++++++++++++---- src/leap/common/keymanager/util.py | 12 +- src/leap/common/tests/test_keymanager.py | 148 +++++++++++++++------ 7 files changed, 333 insertions(+), 81 deletions(-) diff --git a/src/leap/common/keymanager/__init__.py b/src/leap/common/keymanager/__init__.py index a195724..f939a4e 100644 --- a/src/leap/common/keymanager/__init__.py +++ b/src/leap/common/keymanager/__init__.py @@ -33,8 +33,8 @@ from leap.common.keymanager.errors import ( ) from leap.common.keymanager.openpgp import ( OpenPGPKey, - OpenPGPWrapper, - _encrypt_symmetric, + OpenPGPScheme, + encrypt_sym, ) from leap.common.keymanager.http import HTTPClient @@ -56,7 +56,7 @@ class KeyManager(object): self._address = address self._http_client = HTTPClient(url) self._wrapper_map = { - OpenPGPKey: OpenPGPWrapper(soledad), + OpenPGPKey: OpenPGPScheme(soledad), # other types of key will be added to this mapper. } @@ -95,7 +95,7 @@ class KeyManager(object): if send_private: privkey = json.loads( self.get_key(self._address, ktype, private=True).get_json()) - privkey.key_data = _encrypt_symmetric(data, passphrase) + privkey.key_data = encrypt_sym(data, passphrase) data['keys'].append(privkey) headers = None # TODO: replace for token-based-auth self._http_client.request( diff --git a/src/leap/common/keymanager/errors.py b/src/leap/common/keymanager/errors.py index 886c666..add6a38 100644 --- a/src/leap/common/keymanager/errors.py +++ b/src/leap/common/keymanager/errors.py @@ -31,3 +31,10 @@ class KeyAlreadyExists(Exception): """ Raised when attempted to create a key that already exists. """ + + +class KeyAttributesDiffer(Exception): + """ + Raised when trying to delete a key but the stored key differs from the key + passed to the delete_key() method. + """ diff --git a/src/leap/common/keymanager/gpg.py b/src/leap/common/keymanager/gpg.py index 5571ace..f3e6453 100644 --- a/src/leap/common/keymanager/gpg.py +++ b/src/leap/common/keymanager/gpg.py @@ -394,4 +394,4 @@ class GPGWrapper(gnupg.GPG): @return: Whether data is encrypted to a key. @rtype: bool """ - self.is_encrypted_asym() or self.is_encrypted_sym() + return self.is_encrypted_asym(data) or self.is_encrypted_sym(data) diff --git a/src/leap/common/keymanager/keys.py b/src/leap/common/keymanager/keys.py index bed407c..250c2fa 100644 --- a/src/leap/common/keymanager/keys.py +++ b/src/leap/common/keymanager/keys.py @@ -17,7 +17,7 @@ """ -Abstact key type and wrapper representations. +Abstact key type and encryption scheme representations. """ @@ -86,22 +86,23 @@ class EncryptionKey(object): # -# Key wrappers +# Encryption schemes # -class KeyTypeWrapper(object): +class EncryptionScheme(object): """ - Abstract class for Key Type Wrappers. + Abstract class for Encryption Schemes. - A wrapper for a certain key type should know how to get and put keys in - local storage using Soledad and also how to generate new keys. + A wrapper for a certain encryption schemes should know how to get and put + keys in local storage using Soledad, how to generate new keys and how to + find out about possibly encrypted content. """ __metaclass__ = ABCMeta def __init__(self, soledad): """ - Initialize the Key Type Wrapper. + Initialize this Encryption Scheme. @param soledad: A Soledad instance for local storage of keys. @type soledad: leap.soledad.Soledad @@ -139,6 +140,16 @@ class KeyTypeWrapper(object): @param address: The address bound to the key. @type address: str + @return: The key bound to C{address}. @rtype: EncryptionKey """ + + @abstractmethod + def delete_key(self, key): + """ + Remove C{key} from storage. + + @param key: The key to be removed. + @type key: EncryptionKey + """ diff --git a/src/leap/common/keymanager/openpgp.py b/src/leap/common/keymanager/openpgp.py index cd37138..ace8c1e 100644 --- a/src/leap/common/keymanager/openpgp.py +++ b/src/leap/common/keymanager/openpgp.py @@ -32,7 +32,7 @@ from leap.common.keymanager.errors import ( ) from leap.common.keymanager.keys import ( EncryptionKey, - KeyTypeWrapper, + EncryptionScheme, ) from leap.common.keymanager.gpg import GPGWrapper from leap.common.keymanager.util import ( @@ -46,29 +46,137 @@ from leap.common.keymanager.util import ( # Utility functions # -def _encrypt_symmetric(data, password): +def encrypt_sym(data, passphrase): """ - Encrypt C{data} with C{password}. + Encrypt C{data} with C{passphrase}. - This function uses the OpenPGP wrapper to perform the encryption. + @param data: The data to be encrypted. + @type data: str + @param passphrase: The passphrase used to encrypt C{data}. + @type passphrase: str + + @return: The encrypted data. + @rtype: str + """ + + def _encrypt_cb(gpg): + return str( + gpg.encrypt( + data, None, passphrase=passphrase, symmetric=True)) + + return _safe_call(_encrypt_cb) + + +def decrypt_sym(data, passphrase): + """ + Decrypt C{data} with C{passphrase}. + + @param data: The data to be decrypted. + @type data: str + @param passphrase: The passphrase used to decrypt C{data}. + @type passphrase: str + + @return: The decrypted data. + @rtype: str + """ + + def _decrypt_cb(gpg): + return str(gpg.decrypt(data, passphrase=passphrase)) + + return _safe_call(_decrypt_cb) + + +def encrypt_asym(data, key): + """ + Encrypt C{data} using public @{key}. @param data: The data to be encrypted. @type data: str - @param password: The password used to encrypt C{data}. - @type password: str + @param key: The key used to encrypt. + @type key: OpenPGPKey + @return: The encrypted data. @rtype: str """ - cyphertext = None + leap_assert(key.private is False, 'Key is not public.') def _encrypt_cb(gpg): - cyphertext = str( + return str( gpg.encrypt( - data, None, passphrase=password, symmetric=True)) - data['keys'].append(privkey) + data, key.fingerprint, symmetric=False)) + + return _safe_call(_encrypt_cb, key.key_data) + + +def decrypt_asym(data, key): + """ + Decrypt C{data} using private @{key}. + + @param data: The data to be decrypted. + @type data: str + @param key: The key used to decrypt. + @type key: OpenPGPKey + + @return: The decrypted data. + @rtype: str + """ + leap_assert(key.private is True, 'Key is not private.') - _safe_call(_encrypt_cb) - return cyphertext + def _decrypt_cb(gpg): + return str(gpg.decrypt(data)) + + return _safe_call(_decrypt_cb, key.key_data) + + +def is_encrypted(data): + """ + Return whether C{data} was encrypted using OpenPGP. + + @param data: The data we want to know about. + @type data: str + + @return: Whether C{data} was encrypted using this wrapper. + @rtype: bool + """ + + def _is_encrypted_cb(gpg): + return gpg.is_encrypted(data) + + return _safe_call(_is_encrypted_cb) + + +def is_encrypted_sym(data): + """ + Return whether C{data} was encrypted using a public OpenPGP key. + + @param data: The data we want to know about. + @type data: str + + @return: Whether C{data} was encrypted using this wrapper. + @rtype: bool + """ + + def _is_encrypted_cb(gpg): + return gpg.is_encrypted_sym(data) + + return _safe_call(_is_encrypted_cb) + + +def is_encrypted_asym(data): + """ + Return whether C{data} was asymmetrically encrypted using OpenPGP. + + @param data: The data we want to know about. + @type data: str + + @return: Whether C{data} was encrypted using this wrapper. + @rtype: bool + """ + + def _is_encrypted_cb(gpg): + return gpg.is_encrypted_asym(data) + + return _safe_call(_is_encrypted_cb) def _build_key_from_gpg(address, key, key_data): @@ -154,10 +262,14 @@ def _safe_call(callback, key_data=None, **kwargs): @type key_data: str @param **kwargs: Other eventual parameters for the callback. @type **kwargs: **dict + + @return: The results of the callback. + @rtype: str or bool """ gpg = _build_unitary_gpgwrapper(key_data) - callback(gpg, **kwargs) + val = callback(gpg, **kwargs) _destroy_unitary_gpgwrapper(gpg) + return val # @@ -170,7 +282,7 @@ class OpenPGPKey(EncryptionKey): """ -class OpenPGPWrapper(KeyTypeWrapper): +class OpenPGPScheme(EncryptionScheme): """ A wrapper for OpenPGP keys. """ @@ -182,8 +294,7 @@ class OpenPGPWrapper(KeyTypeWrapper): @param soledad: A Soledad instance for key storage. @type soledad: leap.soledad.Soledad """ - KeyTypeWrapper.__init__(self, soledad) - self._soledad = soledad + EncryptionScheme.__init__(self, soledad) def gen_key(self, address): """ @@ -223,10 +334,13 @@ class OpenPGPWrapper(KeyTypeWrapper): leap_assert( re.match('.*<%s>$' % address, key['uids'][0]) is not None, 'Key not correctly bound to address.') - openpgp_key = _build_key_from_gpg( - address, key, - gpg.export_keys(key['fingerprint'])) - self.put_key(openpgp_key) + # insert both public and private keys in storage + for secret in [True, False]: + key = gpg.list_keys(secret=secret).pop() + openpgp_key = _build_key_from_gpg( + address, key, + gpg.export_keys(key['fingerprint'], secret=secret)) + self.put_key(openpgp_key) _safe_call(_gen_key_cb) return self.get_key(address, private=True) @@ -262,15 +376,38 @@ class OpenPGPWrapper(KeyTypeWrapper): def _put_key_raw_cb(gpg): - key = gpg.list_keys(secret=False).pop() # unitary keyring + privkey = None + pubkey = None + try: + privkey = gpg.list_keys(secret=True).pop() + except IndexError: + pass + pubkey = gpg.list_keys(secret=False).pop() # unitary keyring # extract adress from first uid on key - match = re.match('.*<([\w.-]+@[\w.-]+)>.*', key['uids'].pop()) + match = re.match('.*<([\w.-]+@[\w.-]+)>.*', pubkey['uids'].pop()) leap_assert(match is not None, 'No user address in key data.') address = match.group(1) - openpgp_key = _build_key_from_gpg( - address, key, - gpg.export_keys(key['fingerprint'])) - self.put_key(openpgp_key) + if privkey is not None: + match = re.match( + '.*<([\w.-]+@[\w.-]+)>.*', privkey['uids'].pop()) + leap_assert(match is not None, 'No user address in key data.') + privaddress = match.group(1) + leap_assert( + address == privaddress, + 'Addresses in pub and priv key differ.') + leap_assert( + pubkey['fingerprint'] == privkey['fingerprint'], + 'Fingerprints for pub and priv key differ.') + # insert private key in storage + openpgp_privkey = _build_key_from_gpg( + address, privkey, + gpg.export_keys(privkey['fingerprint'], secret=True)) + self.put_key(openpgp_privkey) + # insert public key in storage + openpgp_pubkey = _build_key_from_gpg( + address, pubkey, + gpg.export_keys(pubkey['fingerprint'], secret=False)) + self.put_key(openpgp_pubkey) _safe_call(_put_key_raw_cb, data) @@ -285,7 +422,8 @@ class OpenPGPWrapper(KeyTypeWrapper): if doc is None: self._soledad.create_doc_from_json( key.get_json(), - doc_id=_keymanager_doc_id(key.address, key.private)) + doc_id=_keymanager_doc_id( + OpenPGPKey, key.address, key.private)) else: doc.set_json(key.get_json()) self._soledad.put_doc(doc) @@ -303,4 +441,22 @@ class OpenPGPWrapper(KeyTypeWrapper): @return: The document with the key or None if it does not exist. @rtype: leap.soledad.backends.leap_backend.LeapDocument """ - return self._soledad.get_doc(_keymanager_doc_id(address, private)) + return self._soledad.get_doc( + _keymanager_doc_id(OpenPGPKey, address, private)) + + def delete_key(self, key): + """ + Remove C{key} from storage. + + @param key: The key to be removed. + @type key: EncryptionKey + """ + leap_assert(key.__class__ is OpenPGPKey, 'Wrong key type.') + stored_key = self.get_key(key.address, private=key.private) + if stored_key is None: + raise KeyDoesNotExist(key) + if stored_key.__dict__ != key.__dict__: + raise KeyAttributesDiffer(key) + doc = self._soledad.get_doc( + _keymanager_doc_id(OpenPGPKey, key.address, key.private)) + self._soledad.delete_doc(doc) diff --git a/src/leap/common/keymanager/util.py b/src/leap/common/keymanager/util.py index 42168c8..667d2b2 100644 --- a/src/leap/common/keymanager/util.py +++ b/src/leap/common/keymanager/util.py @@ -25,6 +25,9 @@ import re from hashlib import sha256 +from binascii import b2a_base64 + + from leap.common.check import leap_assert @@ -79,11 +82,13 @@ def _build_key_from_doc(kClass, address, doc): return _build_key_from_dict(kClass, address, doc.content) -def _keymanager_doc_id(address, private=False): +def _keymanager_doc_id(ktype, address, private=False): """ Return the document id for the document containing a key for C{address}. + @param address: The type of the key. + @type address: KeyType @param address: The address bound to the key. @type address: str @param private: Whether the key is private or not. @@ -93,5 +98,6 @@ def _keymanager_doc_id(address, private=False): @rtype: str """ leap_assert(_is_address(address), "Wrong address format: %s" % address) - ktype = 'private' if private else 'public' - return sha256('key-manager-'+address+'-'+ktype).hexdigest() + ktype = str(ktype) + visibility = 'private' if private else 'public' + return sha256('key-manager-'+address+'-'+ktype+'-'+visibility).hexdigest() diff --git a/src/leap/common/tests/test_keymanager.py b/src/leap/common/tests/test_keymanager.py index 4a2693e..f9b478f 100644 --- a/src/leap/common/tests/test_keymanager.py +++ b/src/leap/common/tests/test_keymanager.py @@ -26,14 +26,17 @@ import unittest from leap.common.testing.basetest import BaseLeapTest from leap.soledad import Soledad +from leap.soledad.crypto import SoledadCrypto + + from leap.common.keymanager import KeyManager, openpgp, KeyNotFound from leap.common.keymanager.openpgp import OpenPGPKey -from leap.common.keymanager.gpg import GPGWrapper from leap.common.keymanager.util import ( _is_address, _build_key_from_dict, _keymanager_doc_id, ) +from leap.common.keymanager import errors class KeyManagerUtilTestCase(BaseLeapTest): @@ -72,32 +75,46 @@ class KeyManagerUtilTestCase(BaseLeapTest): 'validation': 'validation', } key = _build_key_from_dict(OpenPGPKey, 'leap@leap.se', kdict) - self.assertEqual(kdict['address'], key.address, + self.assertEqual( + kdict['address'], key.address, 'Wrong data in key.') - self.assertEqual(kdict['key_id'], key.key_id, + self.assertEqual( + kdict['key_id'], key.key_id, 'Wrong data in key.') - self.assertEqual(kdict['fingerprint'], key.fingerprint, + self.assertEqual( + kdict['fingerprint'], key.fingerprint, 'Wrong data in key.') - self.assertEqual(kdict['key_data'], key.key_data, + self.assertEqual( + kdict['key_data'], key.key_data, 'Wrong data in key.') - self.assertEqual(kdict['private'], key.private, + self.assertEqual( + kdict['private'], key.private, 'Wrong data in key.') - self.assertEqual(kdict['length'], key.length, + self.assertEqual( + kdict['length'], key.length, 'Wrong data in key.') - self.assertEqual(kdict['expiry_date'], key.expiry_date, + self.assertEqual( + kdict['expiry_date'], key.expiry_date, 'Wrong data in key.') - self.assertEqual(kdict['first_seen_at'], key.first_seen_at, + self.assertEqual( + kdict['first_seen_at'], key.first_seen_at, 'Wrong data in key.') - self.assertEqual(kdict['last_audited_at'], key.last_audited_at, + self.assertEqual( + kdict['last_audited_at'], key.last_audited_at, 'Wrong data in key.') - self.assertEqual(kdict['validation'], key.validation, + self.assertEqual( + kdict['validation'], key.validation, 'Wrong data in key.') def test__keymanager_doc_id(self): - doc_id1 = _keymanager_doc_id('leap@leap.se', private=False) - doc_id2 = _keymanager_doc_id('leap@leap.se', private=True) - doc_id3 = _keymanager_doc_id('user@leap.se', private=False) - doc_id4 = _keymanager_doc_id('user@leap.se', private=True) + doc_id1 = _keymanager_doc_id( + OpenPGPKey, 'leap@leap.se', private=False) + doc_id2 = _keymanager_doc_id( + OpenPGPKey, 'leap@leap.se', private=True) + doc_id3 = _keymanager_doc_id( + OpenPGPKey, 'user@leap.se', private=False) + doc_id4 = _keymanager_doc_id( + OpenPGPKey, 'user@leap.se', private=True) self.assertFalse(doc_id1 == doc_id2, 'Doc ids are equal!') self.assertFalse(doc_id1 == doc_id3, 'Doc ids are equal!') self.assertFalse(doc_id1 == doc_id4, 'Doc ids are equal!') @@ -119,7 +136,7 @@ class KeyManagerCryptoTestCase(BaseLeapTest): ) # initialize solead by hand for testing purposes self._soledad._init_dirs() - self._soledad._gpg = GPGWrapper(gnupghome=self.tempdir+"/gnupg") + self._soledad._crypto = SoledadCrypto(self._soledad) self._soledad._shared_db = None self._soledad._init_keys() self._soledad._init_db() @@ -130,31 +147,86 @@ class KeyManagerCryptoTestCase(BaseLeapTest): def _key_manager(user='user@leap.se', url='https://domain.org:6425'): return KeyManager(user, url) - def test_openpgp_gen_key(self): - pgp = openpgp.OpenPGPWrapper(self._soledad) - try: - pgp.get_key('user@leap.se') - except KeyNotFound: - key = pgp.gen_key('user@leap.se') - self.assertIsInstance(key, openpgp.OpenPGPKey) - self.assertEqual( - 'user@leap.se', key.address, 'Wrong address bound to key.') - self.assertEqual( - '4096', key.length, 'Wrong key length.') + def _test_openpgp_gen_key(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + self.assertRaises(KeyNotFound, pgp.get_key, 'user@leap.se') + key = pgp.gen_key('user@leap.se') + self.assertIsInstance(key, openpgp.OpenPGPKey) + self.assertEqual( + 'user@leap.se', key.address, 'Wrong address bound to key.') + self.assertEqual( + '4096', key.length, 'Wrong key length.') + + def test_openpgp_put_delete_key(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + self.assertRaises(KeyNotFound, pgp.get_key, 'leap@leap.se') + pgp.put_key_raw(PUBLIC_KEY) + key = pgp.get_key('leap@leap.se', private=False) + pgp.delete_key(key) + self.assertRaises(KeyNotFound, pgp.get_key, 'leap@leap.se') def test_openpgp_put_key_raw(self): - pgp = openpgp.OpenPGPWrapper(self._soledad) - try: - pgp.get_key('leap@leap.se') - except KeyNotFound: - pgp.put_key_raw(PUBLIC_KEY) - key = pgp.get_key('leap@leap.se') - self.assertIsInstance(key, openpgp.OpenPGPKey) - self.assertEqual( - 'leap@leap.se', key.address, 'Wrong address bound to key.') - self.assertEqual( - '4096', key.length, 'Wrong key length.') + pgp = openpgp.OpenPGPScheme(self._soledad) + self.assertRaises(KeyNotFound, pgp.get_key, 'leap@leap.se') + pgp.put_key_raw(PUBLIC_KEY) + key = pgp.get_key('leap@leap.se', private=False) + self.assertIsInstance(key, openpgp.OpenPGPKey) + self.assertEqual( + 'leap@leap.se', key.address, 'Wrong address bound to key.') + self.assertEqual( + '4096', key.length, 'Wrong key length.') + pgp.delete_key(key) + self.assertRaises(KeyNotFound, pgp.get_key, 'leap@leap.se') + + def test_get_public_key(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + self.assertRaises(KeyNotFound, pgp.get_key, 'leap@leap.se') + pgp.put_key_raw(PUBLIC_KEY) + self.assertRaises( + KeyNotFound, pgp.get_key, 'leap@leap.se', private=True) + key = pgp.get_key('leap@leap.se', private=False) + self.assertEqual('leap@leap.se', key.address) + self.assertFalse(key.private) + self.assertEqual(KEY_FINGERPRINT, key.fingerprint) + pgp.delete_key(key) + self.assertRaises(KeyNotFound, pgp.get_key, 'leap@leap.se') + + def test_openpgp_encrypt_decrypt_asym(self): + # encrypt + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_key_raw(PUBLIC_KEY) + pubkey = pgp.get_key('leap@leap.se', private=False) + cyphertext = openpgp.encrypt_asym('data', pubkey) + # assert + self.assertTrue(cyphertext is not None) + self.assertTrue(cyphertext != '') + self.assertTrue(cyphertext != 'data') + self.assertTrue(openpgp.is_encrypted_asym(cyphertext)) + self.assertFalse(openpgp.is_encrypted_sym(cyphertext)) + self.assertTrue(openpgp.is_encrypted(cyphertext)) + # decrypt + self.assertRaises( + KeyNotFound, pgp.get_key, 'leap@leap.se', private=True) + pgp.put_key_raw(PRIVATE_KEY) + privkey = pgp.get_key('leap@leap.se', private=True) + plaintext = openpgp.decrypt_asym(cyphertext, privkey) + pgp.delete_key(pubkey) + pgp.delete_key(privkey) + self.assertRaises( + KeyNotFound, pgp.get_key, 'leap@leap.se', private=False) + self.assertRaises( + KeyNotFound, pgp.get_key, 'leap@leap.se', private=True) + def test_openpgp_encrypt_decrypt_sym(self): + cyphertext = openpgp.encrypt_sym('data', 'pass') + self.assertTrue(cyphertext is not None) + self.assertTrue(cyphertext != '') + self.assertTrue(cyphertext != 'data') + self.assertTrue(openpgp.is_encrypted_sym(cyphertext)) + self.assertFalse(openpgp.is_encrypted_asym(cyphertext)) + self.assertTrue(openpgp.is_encrypted(cyphertext)) + plaintext = openpgp.decrypt_sym(cyphertext, 'pass') + self.assertEqual('data', plaintext) # Key material for testing -- cgit v1.2.3 From 4113dd985b9b5fc3b8e9839670ac5f7416f3f634 Mon Sep 17 00:00:00 2001 From: drebs Date: Sat, 27 Apr 2013 00:06:01 -0300 Subject: Add key refreshing for KeyManager. --- src/leap/common/keymanager/__init__.py | 103 +++++++++++++++++++++++++++---- src/leap/common/keymanager/keys.py | 71 +++++++++++++++++++++ src/leap/common/keymanager/openpgp.py | 20 +++--- src/leap/common/keymanager/util.py | 103 ------------------------------- src/leap/common/tests/test_keymanager.py | 68 ++++++++++++++------ 5 files changed, 218 insertions(+), 147 deletions(-) delete mode 100644 src/leap/common/keymanager/util.py diff --git a/src/leap/common/keymanager/__init__.py b/src/leap/common/keymanager/__init__.py index f939a4e..82fa99b 100644 --- a/src/leap/common/keymanager/__init__.py +++ b/src/leap/common/keymanager/__init__.py @@ -31,6 +31,9 @@ from leap.common.keymanager.errors import ( KeyNotFound, KeyAlreadyExists, ) +from leap.common.keymanager.keys import ( + build_key_from_dict, +) from leap.common.keymanager.openpgp import ( OpenPGPKey, OpenPGPScheme, @@ -39,6 +42,14 @@ from leap.common.keymanager.openpgp import ( from leap.common.keymanager.http import HTTPClient +TAGS_INDEX = 'by-tags' +TAGS_AND_PRIVATE_INDEX = 'by-tags-and-private' +INDEXES = { + TAGS_INDEX: ['tags'], + TAGS_AND_PRIVATE_INDEX: ['tags', 'bool(private)'], +} + + class KeyManager(object): def __init__(self, address, url, soledad): @@ -55,10 +66,45 @@ class KeyManager(object): """ self._address = address self._http_client = HTTPClient(url) + self._soledad = soledad self._wrapper_map = { OpenPGPKey: OpenPGPScheme(soledad), # other types of key will be added to this mapper. } + self._init_indexes() + + # + # utilities + # + + def _key_class_from_type(self, ktype): + """ + Return key class from string representation of key type. + """ + return filter( + lambda klass: str(klass) == ktype, + self._wrapper_map).pop() + + def _init_indexes(self): + """ + Initialize the database indexes. + """ + # Ask the database for currently existing indexes. + db_indexes = dict(self._soledad.list_indexes()) + # Loop through the indexes we expect to find. + for name, expression in INDEXES.items(): + if name not in db_indexes: + # The index does not yet exist. + self._soledad.create_index(name, *expression) + continue + if expression == db_indexes[name]: + # The index exists and is up to date. + continue + # The index exists but the definition is not what expected, so we + # delete it and add the proper index expression. + self._soledad.delete_index(name) + self._soledad.create_index(name, *expression) + def send_key(self, ktype, send_private=False, password=None): """ @@ -104,7 +150,7 @@ class KeyManager(object): json.dumps(data), headers) - def get_key(self, address, ktype, private=False): + def get_key(self, address, ktype, private=False, fetch_remote=True): """ Return a key of type C{ktype} bound to C{address}. @@ -129,14 +175,21 @@ class KeyManager(object): try: return self._wrapper_map[ktype].get_key(address, private=private) except KeyNotFound: - key = filter(lambda k: isinstance(k, ktype), - self._fetch_keys(address)) - if key is None: + if fetch_remote is False: + raise + # fetch keys from server and discard unwanted types. + keys = filter(lambda k: isinstance(k, ktype), + self.fetch_keys_from_server(address)) + if len(keys) is 0: raise KeyNotFound() - self._wrapper_map[ktype].put_key(key) + leap_assert( + len(keys) == 1, + 'Got more than one key of type %s for %s.' % + (str(ktype), address)) + self._wrapper_map[ktype].put_key(keys[0]) return key - def _fetch_keys(self, address): + def fetch_keys_from_server(self, address): """ Fetch keys bound to C{address} from nickserver. @@ -153,18 +206,42 @@ class KeyManager(object): leap_assert( keydata['address'] == address, "Fetched key for wrong address.") + keys = [] for key in keydata['keys']: - # find the key class in the mapper - keyCLass = filter( - lambda klass: str(klass) == key['type'], - self._wrapper_map).pop() - yield _build_key_from_dict(kClass, address, key) + keys.append( + build_key_from_dict( + self._key_class_from_type(key['type']), + address, + key)) + return keys + + def get_all_keys_in_local_db(self, private=False): + """ + Return all keys stored in local database. + + @return: A list with all keys in local db. + @rtype: list + """ + return map( + lambda doc: build_key_from_dict( + self._key_class_from_type(doc.content['type']), + doc.content['address'], + doc.content), + self._soledad.get_from_index( + TAGS_AND_PRIVATE_INDEX, + 'keymanager-key', + '1' if private else '0')) def refresh_keys(self): """ - Update the user's db of validated keys to see if there are changes. + Fetch keys from nickserver and update them locally. """ - raise NotImplementedError(self.refresh_keys) + addresses = set(map( + lambda doc: doc.address, + self.get_all_keys_in_local_db(False))) + for address in addresses: + for key in self.fetch_keys_from_server(address): + self._wrapper_map[key.__class__].put_key(key) def gen_key(self, ktype): """ diff --git a/src/leap/common/keymanager/keys.py b/src/leap/common/keymanager/keys.py index 250c2fa..453e0ed 100644 --- a/src/leap/common/keymanager/keys.py +++ b/src/leap/common/keymanager/keys.py @@ -25,11 +25,81 @@ try: import simplejson as json except ImportError: import json # noqa +import re +from hashlib import sha256 from abc import ABCMeta, abstractmethod +from leap.common.check import leap_assert +# +# Key handling utilities +# + +def is_address(address): + """ + Return whether the given C{address} is in the form user@provider. + + @param address: The address to be tested. + @type address: str + @return: Whether C{address} is in the form user@provider. + @rtype: bool + """ + return bool(re.match('[\w.-]+@[\w.-]+', address)) + + +def build_key_from_dict(kClass, address, kdict): + """ + Build an C{kClass} key bound to C{address} based on info in C{kdict}. + + @param address: The address bound to the key. + @type address: str + @param kdict: Dictionary with key data. + @type kdict: dict + @return: An instance of the key. + @rtype: C{kClass} + """ + leap_assert(address == kdict['address'], 'Wrong address in key data.') + return kClass( + address, + key_id=kdict['key_id'], + fingerprint=kdict['fingerprint'], + key_data=kdict['key_data'], + private=kdict['private'], + length=kdict['length'], + expiry_date=kdict['expiry_date'], + first_seen_at=kdict['first_seen_at'], + last_audited_at=kdict['last_audited_at'], + validation=kdict['validation'], # TODO: verify for validation. + ) + + +def keymanager_doc_id(ktype, address, private=False): + """ + Return the document id for the document containing a key for + C{address}. + + @param address: The type of the key. + @type address: KeyType + @param address: The address bound to the key. + @type address: str + @param private: Whether the key is private or not. + @type private: bool + @return: The document id for the document that stores a key bound to + C{address}. + @rtype: str + """ + leap_assert(is_address(address), "Wrong address format: %s" % address) + ktype = str(ktype) + visibility = 'private' if private else 'public' + return sha256('keymanager-'+address+'-'+ktype+'-'+visibility).hexdigest() + + +# +# Abstraction for encryption keys +# + class EncryptionKey(object): """ Abstract class for encryption keys. @@ -82,6 +152,7 @@ class EncryptionKey(object): 'validation': self.validation, 'first_seen_at': self.first_seen_at, 'last_audited_at': self.last_audited_at, + 'tags': ['keymanager-key'], }) diff --git a/src/leap/common/keymanager/openpgp.py b/src/leap/common/keymanager/openpgp.py index ace8c1e..fa3f732 100644 --- a/src/leap/common/keymanager/openpgp.py +++ b/src/leap/common/keymanager/openpgp.py @@ -33,13 +33,11 @@ from leap.common.keymanager.errors import ( from leap.common.keymanager.keys import ( EncryptionKey, EncryptionScheme, + is_address, + keymanager_doc_id, + build_key_from_dict, ) from leap.common.keymanager.gpg import GPGWrapper -from leap.common.keymanager.util import ( - _is_address, - _build_key_from_doc, - _keymanager_doc_id, -) # @@ -307,7 +305,7 @@ class OpenPGPScheme(EncryptionScheme): @raise KeyAlreadyExists: If key already exists in local database. """ # make sure the key does not already exist - leap_assert(_is_address(address), 'Not an user address: %s' % address) + leap_assert(is_address(address), 'Not an user address: %s' % address) try: self.get_key(address) raise KeyAlreadyExists(address) @@ -358,11 +356,11 @@ class OpenPGPScheme(EncryptionScheme): @rtype: OpenPGPKey @raise KeyNotFound: If the key was not found on local storage. """ - leap_assert(_is_address(address), 'Not an user address: %s' % address) + leap_assert(is_address(address), 'Not an user address: %s' % address) doc = self._get_key_doc(address, private) if doc is None: raise KeyNotFound(address) - return _build_key_from_doc(OpenPGPKey, address, doc) + return build_key_from_dict(OpenPGPKey, address, doc.content) def put_key_raw(self, data): """ @@ -422,7 +420,7 @@ class OpenPGPScheme(EncryptionScheme): if doc is None: self._soledad.create_doc_from_json( key.get_json(), - doc_id=_keymanager_doc_id( + doc_id=keymanager_doc_id( OpenPGPKey, key.address, key.private)) else: doc.set_json(key.get_json()) @@ -442,7 +440,7 @@ class OpenPGPScheme(EncryptionScheme): @rtype: leap.soledad.backends.leap_backend.LeapDocument """ return self._soledad.get_doc( - _keymanager_doc_id(OpenPGPKey, address, private)) + keymanager_doc_id(OpenPGPKey, address, private)) def delete_key(self, key): """ @@ -458,5 +456,5 @@ class OpenPGPScheme(EncryptionScheme): if stored_key.__dict__ != key.__dict__: raise KeyAttributesDiffer(key) doc = self._soledad.get_doc( - _keymanager_doc_id(OpenPGPKey, key.address, key.private)) + keymanager_doc_id(OpenPGPKey, key.address, key.private)) self._soledad.delete_doc(doc) diff --git a/src/leap/common/keymanager/util.py b/src/leap/common/keymanager/util.py deleted file mode 100644 index 667d2b2..0000000 --- a/src/leap/common/keymanager/util.py +++ /dev/null @@ -1,103 +0,0 @@ -# -*- coding: utf-8 -*- -# util.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -""" -Utilities for the Key Manager. -""" - - -import re - - -from hashlib import sha256 -from binascii import b2a_base64 - - -from leap.common.check import leap_assert - - -def _is_address(address): - """ - Return whether the given C{address} is in the form user@provider. - - @param address: The address to be tested. - @type address: str - @return: Whether C{address} is in the form user@provider. - @rtype: bool - """ - return bool(re.match('[\w.-]+@[\w.-]+', address)) - - -def _build_key_from_dict(kClass, address, kdict): - """ - Build an C{kClass} key bound to C{address} based on info in C{kdict}. - - @param address: The address bound to the key. - @type address: str - @param kdict: Dictionary with key data. - @type kdict: dict - @return: An instance of the key. - @rtype: C{kClass} - """ - return kClass( - address, - key_id=kdict['key_id'], - fingerprint=kdict['fingerprint'], - key_data=kdict['key_data'], - private=kdict['private'], - length=kdict['length'], - expiry_date=kdict['expiry_date'], - first_seen_at=kdict['first_seen_at'], - last_audited_at=kdict['last_audited_at'], - validation=kdict['validation'], # TODO: verify for validation. - ) - - -def _build_key_from_doc(kClass, address, doc): - """ - Build an C{kClass} for C{address} based on C{doc} from local storage. - - @param address: The address bound to the key. - @type address: str - @param doc: Document obtained from Soledad storage. - @type doc: leap.soledad.backends.leap_backend.LeapDocument - @return: An instance of the key. - @rtype: C{kClass} - """ - return _build_key_from_dict(kClass, address, doc.content) - - -def _keymanager_doc_id(ktype, address, private=False): - """ - Return the document id for the document containing a key for - C{address}. - - @param address: The type of the key. - @type address: KeyType - @param address: The address bound to the key. - @type address: str - @param private: Whether the key is private or not. - @type private: bool - @return: The document id for the document that stores a key bound to - C{address}. - @rtype: str - """ - leap_assert(_is_address(address), "Wrong address format: %s" % address) - ktype = str(ktype) - visibility = 'private' if private else 'public' - return sha256('key-manager-'+address+'-'+ktype+'-'+visibility).hexdigest() diff --git a/src/leap/common/tests/test_keymanager.py b/src/leap/common/tests/test_keymanager.py index f9b478f..9bafb89 100644 --- a/src/leap/common/tests/test_keymanager.py +++ b/src/leap/common/tests/test_keymanager.py @@ -29,12 +29,18 @@ from leap.soledad import Soledad from leap.soledad.crypto import SoledadCrypto -from leap.common.keymanager import KeyManager, openpgp, KeyNotFound +from leap.common.keymanager import ( + KeyManager, + openpgp, + KeyNotFound, + TAGS_INDEX, + TAGS_AND_PRIVATE_INDEX, +) from leap.common.keymanager.openpgp import OpenPGPKey -from leap.common.keymanager.util import ( - _is_address, - _build_key_from_dict, - _keymanager_doc_id, +from leap.common.keymanager.keys import ( + is_address, + build_key_from_dict, + keymanager_doc_id, ) from leap.common.keymanager import errors @@ -47,21 +53,21 @@ class KeyManagerUtilTestCase(BaseLeapTest): def tearDown(self): pass - def test__is_address(self): + def test_is_address(self): self.assertTrue( - _is_address('user@leap.se'), + is_address('user@leap.se'), 'Incorrect address detection.') self.assertFalse( - _is_address('userleap.se'), + is_address('userleap.se'), 'Incorrect address detection.') self.assertFalse( - _is_address('user@'), + is_address('user@'), 'Incorrect address detection.') self.assertFalse( - _is_address('@leap.se'), + is_address('@leap.se'), 'Incorrect address detection.') - def test__build_key_from_dict(self): + def test_build_key_from_dict(self): kdict = { 'address': 'leap@leap.se', 'key_id': 'key_id', @@ -74,7 +80,7 @@ class KeyManagerUtilTestCase(BaseLeapTest): 'last_audited_at': 'last_audited_at', 'validation': 'validation', } - key = _build_key_from_dict(OpenPGPKey, 'leap@leap.se', kdict) + key = build_key_from_dict(OpenPGPKey, 'leap@leap.se', kdict) self.assertEqual( kdict['address'], key.address, 'Wrong data in key.') @@ -106,14 +112,14 @@ class KeyManagerUtilTestCase(BaseLeapTest): kdict['validation'], key.validation, 'Wrong data in key.') - def test__keymanager_doc_id(self): - doc_id1 = _keymanager_doc_id( + def test_keymanager_doc_id(self): + doc_id1 = keymanager_doc_id( OpenPGPKey, 'leap@leap.se', private=False) - doc_id2 = _keymanager_doc_id( + doc_id2 = keymanager_doc_id( OpenPGPKey, 'leap@leap.se', private=True) - doc_id3 = _keymanager_doc_id( + doc_id3 = keymanager_doc_id( OpenPGPKey, 'user@leap.se', private=False) - doc_id4 = _keymanager_doc_id( + doc_id4 = keymanager_doc_id( OpenPGPKey, 'user@leap.se', private=True) self.assertFalse(doc_id1 == doc_id2, 'Doc ids are equal!') self.assertFalse(doc_id1 == doc_id3, 'Doc ids are equal!') @@ -123,7 +129,7 @@ class KeyManagerUtilTestCase(BaseLeapTest): self.assertFalse(doc_id3 == doc_id4, 'Doc ids are equal!') -class KeyManagerCryptoTestCase(BaseLeapTest): +class KeyManagerWithSoledadTestCase(BaseLeapTest): def setUp(self): self._soledad = Soledad( @@ -144,8 +150,9 @@ class KeyManagerCryptoTestCase(BaseLeapTest): def tearDown(self): pass - def _key_manager(user='user@leap.se', url='https://domain.org:6425'): - return KeyManager(user, url) + + +class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def _test_openpgp_gen_key(self): pgp = openpgp.OpenPGPScheme(self._soledad) @@ -229,6 +236,27 @@ class KeyManagerCryptoTestCase(BaseLeapTest): self.assertEqual('data', plaintext) +class KeyManagerKeyManagementTestCase( + KeyManagerWithSoledadTestCase): + + def _key_manager(self, user='leap@leap.se', url=''): + return KeyManager(user, url, self._soledad) + + def test_get_all_keys_in_db(self): + km = self._key_manager() + km._wrapper_map[OpenPGPKey].put_key_raw(PRIVATE_KEY) + # get public keys + keys = km.get_all_keys_in_local_db(False) + self.assertEqual(len(keys), 1, 'Wrong number of keys') + self.assertEqual('leap@leap.se', keys[0].address) + self.assertFalse(keys[0].private) + # get private keys + keys = km.get_all_keys_in_local_db(True) + self.assertEqual(len(keys), 1, 'Wrong number of keys') + self.assertEqual('leap@leap.se', keys[0].address) + self.assertTrue(keys[0].private) + + # Key material for testing KEY_FINGERPRINT = "E36E738D69173C13D709E44F2F455E2824D18DDF" PUBLIC_KEY = """ -- cgit v1.2.3 From 365c318872e433ee13eff29e37039c10b22ee685 Mon Sep 17 00:00:00 2001 From: drebs Date: Sat, 27 Apr 2013 00:35:22 -0300 Subject: Use 'requests' module in KeyManager. --- src/leap/common/keymanager/__init__.py | 38 ++++++++++------ src/leap/common/keymanager/http.py | 78 -------------------------------- src/leap/common/tests/test_keymanager.py | 3 +- 3 files changed, 25 insertions(+), 94 deletions(-) delete mode 100644 src/leap/common/keymanager/http.py diff --git a/src/leap/common/keymanager/__init__.py b/src/leap/common/keymanager/__init__.py index 82fa99b..8db3b3c 100644 --- a/src/leap/common/keymanager/__init__.py +++ b/src/leap/common/keymanager/__init__.py @@ -21,9 +21,7 @@ Key Manager is a Nicknym agent for LEAP client. """ import httplib - - -from u1db.errors import HTTPError +import requests from leap.common.check import leap_assert @@ -39,7 +37,6 @@ from leap.common.keymanager.openpgp import ( OpenPGPScheme, encrypt_sym, ) -from leap.common.keymanager.http import HTTPClient TAGS_INDEX = 'by-tags' @@ -52,7 +49,7 @@ INDEXES = { class KeyManager(object): - def __init__(self, address, url, soledad): + def __init__(self, address, nickserver_url, soledad): """ Initialize a Key Manager for user's C{address} with provider's nickserver reachable in C{url}. @@ -65,7 +62,7 @@ class KeyManager(object): @type soledad: leap.soledad.Soledad """ self._address = address - self._http_client = HTTPClient(url) + self._nickserver_url = nickserver_url self._soledad = soledad self._wrapper_map = { OpenPGPKey: OpenPGPScheme(soledad), @@ -105,6 +102,22 @@ class KeyManager(object): self._soledad.delete_index(name) self._soledad.create_index(name, *expression) + def _get_dict_from_http_json(self, path): + """ + Make a GET HTTP request and return a dictionary containing the + response. + """ + response = requests.get(self._nickserver_url+path) + leap_assert(r.status_code == 200, 'Invalid response.') + leap_assert( + response.headers['content-type'].startswith('application/json') + is True, + 'Content-type is not JSON.') + return r.json() + + # + # key management + # def send_key(self, ktype, send_private=False, password=None): """ @@ -143,12 +156,10 @@ class KeyManager(object): self.get_key(self._address, ktype, private=True).get_json()) privkey.key_data = encrypt_sym(data, passphrase) data['keys'].append(privkey) - headers = None # TODO: replace for token-based-auth - self._http_client.request( - 'PUT', - '/key/%s' % address, - json.dumps(data), - headers) + requests.put( + self._nickserver_url + '/key/' + address, + data=data, + auth=(self._address, None)) # TODO: replace for token-based auth. def get_key(self, address, ktype, private=False, fetch_remote=True): """ @@ -201,8 +212,7 @@ class KeyManager(object): @raise KeyNotFound: If the key was not found on nickserver. @raise httplib.HTTPException: """ - self._http_client.request('GET', '/key/%s' % address, None, None) - keydata = json.loads(self._http_client.read_response()) + keydata = self._get_dict_from_http_json('/key/%s' % address) leap_assert( keydata['address'] == address, "Fetched key for wrong address.") diff --git a/src/leap/common/keymanager/http.py b/src/leap/common/keymanager/http.py deleted file mode 100644 index 478137d..0000000 --- a/src/leap/common/keymanager/http.py +++ /dev/null @@ -1,78 +0,0 @@ -# -*- coding: utf-8 -*- -# http.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -""" -HTTP utilities. -""" - - -import urlparse -import httplib - - -def HTTPClient(object): - """ - A simple HTTP client for making requests. - """ - - def __init__(self, url): - """ - Initialize the HTTP client. - """ - self._url = urlparse.urlsplit(url) - self._conn = None - - def _ensure_connection(self): - """ - Ensure the creation of the connection object. - """ - if self._conn is not None: - return - if self._url.scheme == 'https': - connClass = httplib.HTTPSConnection - else: - connClass = httplib.HTTPConnection - self._conn = connClass(self._url.hostname, self._url.port) - - def request(method, url_query, body, headers): - """ - Make an HTTP request. - - @param method: The method of the request. - @type method: str - @param url_query: The URL query string of the request. - @type url_query: str - @param body: The body of the request. - @type body: str - @param headers: Headers to be sent on the request. - @type headers: list of str - """ - self._ensure_connection() - return self._conn.request(mthod, url_query, body, headers) - - def response(self): - """ - Return the response of an HTTP request. - """ - return self._conn.getresponse() - - def read_response(self): - """ - Get the contents of a response for an HTTP request. - """ - return self.response().read() diff --git a/src/leap/common/tests/test_keymanager.py b/src/leap/common/tests/test_keymanager.py index 9bafb89..9670f9b 100644 --- a/src/leap/common/tests/test_keymanager.py +++ b/src/leap/common/tests/test_keymanager.py @@ -151,10 +151,9 @@ class KeyManagerWithSoledadTestCase(BaseLeapTest): pass - class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): - def _test_openpgp_gen_key(self): + def test_openpgp_gen_key(self): pgp = openpgp.OpenPGPScheme(self._soledad) self.assertRaises(KeyNotFound, pgp.get_key, 'user@leap.se') key = pgp.gen_key('user@leap.se') -- cgit v1.2.3 From 4e309e8be7dad025b7e30e99e10dbc5fb49f9bf5 Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 29 Apr 2013 16:06:59 -0300 Subject: Remove gpg reference on Soledad usage. --- src/leap/common/tests/test_keymanager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/leap/common/tests/test_keymanager.py b/src/leap/common/tests/test_keymanager.py index 9670f9b..9bf394d 100644 --- a/src/leap/common/tests/test_keymanager.py +++ b/src/leap/common/tests/test_keymanager.py @@ -135,7 +135,6 @@ class KeyManagerWithSoledadTestCase(BaseLeapTest): self._soledad = Soledad( "leap@leap.se", "123456", - gnupg_home=self.tempdir+"/gnupg", secret_path=self.tempdir+"/secret.gpg", local_db_path=self.tempdir+"/soledad.u1db", bootstrap=False, -- cgit v1.2.3 From 852a0fa34a94b588f66e2af0aa628d058c243fd3 Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 29 Apr 2013 16:07:30 -0300 Subject: Remove string conversion for encryption/decryption results. --- src/leap/common/keymanager/openpgp.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/leap/common/keymanager/openpgp.py b/src/leap/common/keymanager/openpgp.py index fa3f732..94d55cc 100644 --- a/src/leap/common/keymanager/openpgp.py +++ b/src/leap/common/keymanager/openpgp.py @@ -58,9 +58,8 @@ def encrypt_sym(data, passphrase): """ def _encrypt_cb(gpg): - return str( - gpg.encrypt( - data, None, passphrase=passphrase, symmetric=True)) + return gpg.encrypt( + data, None, passphrase=passphrase, symmetric=True).data return _safe_call(_encrypt_cb) @@ -79,7 +78,7 @@ def decrypt_sym(data, passphrase): """ def _decrypt_cb(gpg): - return str(gpg.decrypt(data, passphrase=passphrase)) + return gpg.decrypt(data, passphrase=passphrase).data return _safe_call(_decrypt_cb) @@ -99,9 +98,8 @@ def encrypt_asym(data, key): leap_assert(key.private is False, 'Key is not public.') def _encrypt_cb(gpg): - return str( - gpg.encrypt( - data, key.fingerprint, symmetric=False)) + return gpg.encrypt( + data, key.fingerprint, symmetric=False).data return _safe_call(_encrypt_cb, key.key_data) @@ -121,7 +119,7 @@ def decrypt_asym(data, key): leap_assert(key.private is True, 'Key is not private.') def _decrypt_cb(gpg): - return str(gpg.decrypt(data)) + return gpg.decrypt(data).data return _safe_call(_decrypt_cb, key.key_data) -- cgit v1.2.3 From f74c32a4a8e9eae9c8055c25d3848ff7d836e308 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 1 May 2013 04:05:03 +0900 Subject: return None if no key found, avoid KeyError --- setup.py | 2 +- src/leap/common/__init__.py | 2 +- src/leap/common/config/baseconfig.py | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 3c0f9de..d5323bc 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ setup( name='leap.common', # If you change version, do it also in # src/leap/common/__init__.py - version='0.2.1-dev', + version='0.2.1-dev2', url='https://leap.se/', license='GPLv3+', author='The LEAP Encryption Access Project', diff --git a/src/leap/common/__init__.py b/src/leap/common/__init__.py index 19c43c9..a5ea197 100644 --- a/src/leap/common/__init__.py +++ b/src/leap/common/__init__.py @@ -16,4 +16,4 @@ except ImportError: __all__ = ["certs", "check", "files", "events"] -__version__ = "0.2.1-dev" +__version__ = "0.2.1-dev2" diff --git a/src/leap/common/config/baseconfig.py b/src/leap/common/config/baseconfig.py index edb9b24..146f1e4 100644 --- a/src/leap/common/config/baseconfig.py +++ b/src/leap/common/config/baseconfig.py @@ -70,12 +70,11 @@ class BaseConfig: @return: returns the value for the specified key in the config """ leap_assert(self._config_checker, "Load the config first") - return self._config_checker.config[key] + return self._config_checker.config.get(key, None) def get_path_prefix(self): """ Returns the platform dependant path prefixer - """ return get_platform_prefixer().get_path_prefix( standalone=self.standalone) -- cgit v1.2.3 From 170cd90f593a106ea7730babde310724410a585e Mon Sep 17 00:00:00 2001 From: Tomas Touceda Date: Thu, 2 May 2013 15:02:33 -0300 Subject: Various fixes --- pkg/requirements.pip | 1 + src/leap/common/keymanager/__init__.py | 18 +++++++++--------- src/leap/common/keymanager/keys.py | 4 ++++ src/leap/common/keymanager/openpgp.py | 3 ++- src/leap/common/tests/test_keymanager.py | 3 --- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/pkg/requirements.pip b/pkg/requirements.pip index 99d8533..e8f8e87 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -5,3 +5,4 @@ protobuf.socketrpc pyopenssl python-dateutil autopep8 +python-gnupg \ No newline at end of file diff --git a/src/leap/common/keymanager/__init__.py b/src/leap/common/keymanager/__init__.py index 8db3b3c..01dc0da 100644 --- a/src/leap/common/keymanager/__init__.py +++ b/src/leap/common/keymanager/__init__.py @@ -20,14 +20,16 @@ Key Manager is a Nicknym agent for LEAP client. """ -import httplib import requests +try: + import simplejson as json +except ImportError: + import json # noqa from leap.common.check import leap_assert from leap.common.keymanager.errors import ( KeyNotFound, - KeyAlreadyExists, ) from leap.common.keymanager.keys import ( build_key_from_dict, @@ -108,12 +110,12 @@ class KeyManager(object): response. """ response = requests.get(self._nickserver_url+path) - leap_assert(r.status_code == 200, 'Invalid response.') + leap_assert(response.status_code == 200, 'Invalid response.') leap_assert( response.headers['content-type'].startswith('application/json') is True, 'Content-type is not JSON.') - return r.json() + return response.json() # # key management @@ -132,8 +134,6 @@ class KeyManager(object): will be saved in the server in a way it is publicly retrievable through the hash string. - @param address: The address bound to the key. - @type address: str @param ktype: The type of the key. @type ktype: KeyType @@ -154,10 +154,10 @@ class KeyManager(object): if send_private: privkey = json.loads( self.get_key(self._address, ktype, private=True).get_json()) - privkey.key_data = encrypt_sym(data, passphrase) + privkey.key_data = encrypt_sym(data, password) data['keys'].append(privkey) requests.put( - self._nickserver_url + '/key/' + address, + self._nickserver_url + '/key/' + self._address, data=data, auth=(self._address, None)) # TODO: replace for token-based auth. @@ -198,7 +198,7 @@ class KeyManager(object): 'Got more than one key of type %s for %s.' % (str(ktype), address)) self._wrapper_map[ktype].put_key(keys[0]) - return key + return self._wrapper_map[ktype].get_key(address, private=private) def fetch_keys_from_server(self, address): """ diff --git a/src/leap/common/keymanager/keys.py b/src/leap/common/keymanager/keys.py index 453e0ed..2e6bfe9 100644 --- a/src/leap/common/keymanager/keys.py +++ b/src/leap/common/keymanager/keys.py @@ -194,6 +194,7 @@ class EncryptionScheme(object): @rtype: EncryptionKey @raise KeyNotFound: If the key was not found on local storage. """ + pass @abstractmethod def put_key(self, key): @@ -203,6 +204,7 @@ class EncryptionScheme(object): @param key: The key to be stored. @type key: EncryptionKey """ + pass @abstractmethod def gen_key(self, address): @@ -215,6 +217,7 @@ class EncryptionScheme(object): @return: The key bound to C{address}. @rtype: EncryptionKey """ + pass @abstractmethod def delete_key(self, key): @@ -224,3 +227,4 @@ class EncryptionScheme(object): @param key: The key to be removed. @type key: EncryptionKey """ + pass diff --git a/src/leap/common/keymanager/openpgp.py b/src/leap/common/keymanager/openpgp.py index 94d55cc..e2ffe76 100644 --- a/src/leap/common/keymanager/openpgp.py +++ b/src/leap/common/keymanager/openpgp.py @@ -29,6 +29,7 @@ from leap.common.check import leap_assert from leap.common.keymanager.errors import ( KeyNotFound, KeyAlreadyExists, + KeyAttributesDiffer ) from leap.common.keymanager.keys import ( EncryptionKey, @@ -450,7 +451,7 @@ class OpenPGPScheme(EncryptionScheme): leap_assert(key.__class__ is OpenPGPKey, 'Wrong key type.') stored_key = self.get_key(key.address, private=key.private) if stored_key is None: - raise KeyDoesNotExist(key) + raise KeyNotFound(key) if stored_key.__dict__ != key.__dict__: raise KeyAttributesDiffer(key) doc = self._soledad.get_doc( diff --git a/src/leap/common/tests/test_keymanager.py b/src/leap/common/tests/test_keymanager.py index 9bf394d..32bd1fd 100644 --- a/src/leap/common/tests/test_keymanager.py +++ b/src/leap/common/tests/test_keymanager.py @@ -21,9 +21,6 @@ Tests for the Key Manager. """ -import unittest - - from leap.common.testing.basetest import BaseLeapTest from leap.soledad import Soledad from leap.soledad.crypto import SoledadCrypto -- cgit v1.2.3 From 71a3f21d3b72566efa6cf024317dfc96624a10f7 Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 2 May 2013 22:38:31 -0300 Subject: Add tests for key management remote methods. --- src/leap/common/keymanager/__init__.py | 42 ++++++-- src/leap/common/keymanager/errors.py | 6 ++ src/leap/common/tests/test_keymanager.py | 166 ++++++++++++++++++++++++++----- 3 files changed, 176 insertions(+), 38 deletions(-) diff --git a/src/leap/common/keymanager/__init__.py b/src/leap/common/keymanager/__init__.py index 01dc0da..d6dbb8a 100644 --- a/src/leap/common/keymanager/__init__.py +++ b/src/leap/common/keymanager/__init__.py @@ -30,6 +30,7 @@ except ImportError: from leap.common.check import leap_assert from leap.common.keymanager.errors import ( KeyNotFound, + NoPasswordGiven, ) from leap.common.keymanager.keys import ( build_key_from_dict, @@ -51,7 +52,7 @@ INDEXES = { class KeyManager(object): - def __init__(self, address, nickserver_url, soledad): + def __init__(self, address, nickserver_url, soledad, token=None): """ Initialize a Key Manager for user's C{address} with provider's nickserver reachable in C{url}. @@ -66,11 +67,13 @@ class KeyManager(object): self._address = address self._nickserver_url = nickserver_url self._soledad = soledad + self.token = token self._wrapper_map = { OpenPGPKey: OpenPGPScheme(soledad), # other types of key will be added to this mapper. } self._init_indexes() + self._fetcher = requests # # utilities @@ -109,7 +112,7 @@ class KeyManager(object): Make a GET HTTP request and return a dictionary containing the response. """ - response = requests.get(self._nickserver_url+path) + response = self._fetcher.get(self._nickserver_url+path) leap_assert(response.status_code == 200, 'Invalid response.') leap_assert( response.headers['content-type'].startswith('application/json') @@ -142,24 +145,27 @@ class KeyManager(object): keyserver. """ # prepare the public key bound to address + pubkey = self.get_key( + self._address, ktype, private=False, fetch_remote=False) data = { 'address': self._address, 'keys': [ - json.loads( - self.get_key( - self._address, ktype, private=False).get_json()), + json.loads(pubkey.get_json()), ] } # prepare the private key bound to address if send_private: - privkey = json.loads( - self.get_key(self._address, ktype, private=True).get_json()) - privkey.key_data = encrypt_sym(data, password) + if password is None or password == '': + raise NoPasswordGiven('Can\'t send unencrypted private keys!') + privkey = self.get_key( + self._address, ktype, private=True, fetch_remote=False) + privkey = json.loads(privkey.get_json()) + privkey.key_data = encrypt_sym(privkey.key_data, password) data['keys'].append(privkey) - requests.put( + self._fetcher.put( self._nickserver_url + '/key/' + self._address, data=data, - auth=(self._address, None)) # TODO: replace for token-based auth. + auth=(self._address, self._token)) def get_key(self, address, ktype, private=False, fetch_remote=True): """ @@ -248,7 +254,8 @@ class KeyManager(object): """ addresses = set(map( lambda doc: doc.address, - self.get_all_keys_in_local_db(False))) + self.get_all_keys_in_local_db(private=False))) + # TODO: maybe we should not attempt to refresh our own public key? for address in addresses: for key in self.fetch_keys_from_server(address): self._wrapper_map[key.__class__].put_key(key) @@ -264,3 +271,16 @@ class KeyManager(object): @rtype: EncryptionKey """ return self._wrapper_map[ktype].gen_key(self._address) + + # + # Token setter/getter + # + + def _get_token(self): + return self._token + + def _set_token(self, token): + self._token = token + + token = property( + _get_token, _set_token, doc='The auth token.') diff --git a/src/leap/common/keymanager/errors.py b/src/leap/common/keymanager/errors.py index add6a38..1cf506e 100644 --- a/src/leap/common/keymanager/errors.py +++ b/src/leap/common/keymanager/errors.py @@ -38,3 +38,9 @@ class KeyAttributesDiffer(Exception): Raised when trying to delete a key but the stored key differs from the key passed to the delete_key() method. """ + +class NoPasswordGiven(Exception): + """ + Raised when trying to perform some action that needs a password without + providing one. + """ diff --git a/src/leap/common/tests/test_keymanager.py b/src/leap/common/tests/test_keymanager.py index 32bd1fd..1d7a382 100644 --- a/src/leap/common/tests/test_keymanager.py +++ b/src/leap/common/tests/test_keymanager.py @@ -21,6 +21,13 @@ Tests for the Key Manager. """ +import mock +try: + import simplejson as json +except ImportError: + import json # noqa + + from leap.common.testing.basetest import BaseLeapTest from leap.soledad import Soledad from leap.soledad.crypto import SoledadCrypto @@ -30,6 +37,7 @@ from leap.common.keymanager import ( KeyManager, openpgp, KeyNotFound, + NoPasswordGiven, TAGS_INDEX, TAGS_AND_PRIVATE_INDEX, ) @@ -42,6 +50,9 @@ from leap.common.keymanager.keys import ( from leap.common.keymanager import errors +ADDRESS = 'leap@leap.se' + + class KeyManagerUtilTestCase(BaseLeapTest): def setUp(self): @@ -66,7 +77,7 @@ class KeyManagerUtilTestCase(BaseLeapTest): def test_build_key_from_dict(self): kdict = { - 'address': 'leap@leap.se', + 'address': ADDRESS, 'key_id': 'key_id', 'fingerprint': 'fingerprint', 'key_data': 'key_data', @@ -77,7 +88,7 @@ class KeyManagerUtilTestCase(BaseLeapTest): 'last_audited_at': 'last_audited_at', 'validation': 'validation', } - key = build_key_from_dict(OpenPGPKey, 'leap@leap.se', kdict) + key = build_key_from_dict(OpenPGPKey, ADDRESS, kdict) self.assertEqual( kdict['address'], key.address, 'Wrong data in key.') @@ -111,9 +122,9 @@ class KeyManagerUtilTestCase(BaseLeapTest): def test_keymanager_doc_id(self): doc_id1 = keymanager_doc_id( - OpenPGPKey, 'leap@leap.se', private=False) + OpenPGPKey, ADDRESS, private=False) doc_id2 = keymanager_doc_id( - OpenPGPKey, 'leap@leap.se', private=True) + OpenPGPKey, ADDRESS, private=True) doc_id3 = keymanager_doc_id( OpenPGPKey, 'user@leap.se', private=False) doc_id4 = keymanager_doc_id( @@ -134,6 +145,8 @@ class KeyManagerWithSoledadTestCase(BaseLeapTest): "123456", secret_path=self.tempdir+"/secret.gpg", local_db_path=self.tempdir+"/soledad.u1db", + server_url='', + cert_file=None, bootstrap=False, ) # initialize solead by hand for testing purposes @@ -144,7 +157,14 @@ class KeyManagerWithSoledadTestCase(BaseLeapTest): self._soledad._init_db() def tearDown(self): - pass + km = self._key_manager() + for key in km.get_all_keys_in_local_db(): + km._wrapper_map[key.__class__].delete_key(key) + for key in km.get_all_keys_in_local_db(private=True): + km._wrapper_map[key.__class__].delete_key(key) + + def _key_manager(self, user=ADDRESS, url=''): + return KeyManager(user, url, self._soledad) class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): @@ -161,43 +181,43 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_openpgp_put_delete_key(self): pgp = openpgp.OpenPGPScheme(self._soledad) - self.assertRaises(KeyNotFound, pgp.get_key, 'leap@leap.se') + self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) pgp.put_key_raw(PUBLIC_KEY) - key = pgp.get_key('leap@leap.se', private=False) + key = pgp.get_key(ADDRESS, private=False) pgp.delete_key(key) - self.assertRaises(KeyNotFound, pgp.get_key, 'leap@leap.se') + self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) def test_openpgp_put_key_raw(self): pgp = openpgp.OpenPGPScheme(self._soledad) - self.assertRaises(KeyNotFound, pgp.get_key, 'leap@leap.se') + self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) pgp.put_key_raw(PUBLIC_KEY) - key = pgp.get_key('leap@leap.se', private=False) + key = pgp.get_key(ADDRESS, private=False) self.assertIsInstance(key, openpgp.OpenPGPKey) self.assertEqual( - 'leap@leap.se', key.address, 'Wrong address bound to key.') + ADDRESS, key.address, 'Wrong address bound to key.') self.assertEqual( '4096', key.length, 'Wrong key length.') pgp.delete_key(key) - self.assertRaises(KeyNotFound, pgp.get_key, 'leap@leap.se') + self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) def test_get_public_key(self): pgp = openpgp.OpenPGPScheme(self._soledad) - self.assertRaises(KeyNotFound, pgp.get_key, 'leap@leap.se') + self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) pgp.put_key_raw(PUBLIC_KEY) self.assertRaises( - KeyNotFound, pgp.get_key, 'leap@leap.se', private=True) - key = pgp.get_key('leap@leap.se', private=False) - self.assertEqual('leap@leap.se', key.address) + KeyNotFound, pgp.get_key, ADDRESS, private=True) + key = pgp.get_key(ADDRESS, private=False) + self.assertEqual(ADDRESS, key.address) self.assertFalse(key.private) self.assertEqual(KEY_FINGERPRINT, key.fingerprint) pgp.delete_key(key) - self.assertRaises(KeyNotFound, pgp.get_key, 'leap@leap.se') + self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) def test_openpgp_encrypt_decrypt_asym(self): # encrypt pgp = openpgp.OpenPGPScheme(self._soledad) pgp.put_key_raw(PUBLIC_KEY) - pubkey = pgp.get_key('leap@leap.se', private=False) + pubkey = pgp.get_key(ADDRESS, private=False) cyphertext = openpgp.encrypt_asym('data', pubkey) # assert self.assertTrue(cyphertext is not None) @@ -208,16 +228,16 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): self.assertTrue(openpgp.is_encrypted(cyphertext)) # decrypt self.assertRaises( - KeyNotFound, pgp.get_key, 'leap@leap.se', private=True) + KeyNotFound, pgp.get_key, ADDRESS, private=True) pgp.put_key_raw(PRIVATE_KEY) - privkey = pgp.get_key('leap@leap.se', private=True) + privkey = pgp.get_key(ADDRESS, private=True) plaintext = openpgp.decrypt_asym(cyphertext, privkey) pgp.delete_key(pubkey) pgp.delete_key(privkey) self.assertRaises( - KeyNotFound, pgp.get_key, 'leap@leap.se', private=False) + KeyNotFound, pgp.get_key, ADDRESS, private=False) self.assertRaises( - KeyNotFound, pgp.get_key, 'leap@leap.se', private=True) + KeyNotFound, pgp.get_key, ADDRESS, private=True) def test_openpgp_encrypt_decrypt_sym(self): cyphertext = openpgp.encrypt_sym('data', 'pass') @@ -234,23 +254,115 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): class KeyManagerKeyManagementTestCase( KeyManagerWithSoledadTestCase): - def _key_manager(self, user='leap@leap.se', url=''): - return KeyManager(user, url, self._soledad) - def test_get_all_keys_in_db(self): km = self._key_manager() km._wrapper_map[OpenPGPKey].put_key_raw(PRIVATE_KEY) # get public keys keys = km.get_all_keys_in_local_db(False) self.assertEqual(len(keys), 1, 'Wrong number of keys') - self.assertEqual('leap@leap.se', keys[0].address) + self.assertEqual(ADDRESS, keys[0].address) self.assertFalse(keys[0].private) # get private keys keys = km.get_all_keys_in_local_db(True) self.assertEqual(len(keys), 1, 'Wrong number of keys') - self.assertEqual('leap@leap.se', keys[0].address) + self.assertEqual(ADDRESS, keys[0].address) self.assertTrue(keys[0].private) + def test_get_public_key(self): + km = self._key_manager() + km._wrapper_map[OpenPGPKey].put_key_raw(PRIVATE_KEY) + # get the key + key = km.get_key(ADDRESS, OpenPGPKey, private=False, + fetch_remote=False) + self.assertTrue(key is not None) + self.assertEqual(key.address, ADDRESS) + self.assertEqual( + key.fingerprint.lower(), KEY_FINGERPRINT.lower()) + self.assertFalse(key.private) + + def test_get_private_key(self): + km = self._key_manager() + km._wrapper_map[OpenPGPKey].put_key_raw(PRIVATE_KEY) + # get the key + key = km.get_key(ADDRESS, OpenPGPKey, private=True, + fetch_remote=False) + self.assertTrue(key is not None) + self.assertEqual(key.address, ADDRESS) + self.assertEqual( + key.fingerprint.lower(), KEY_FINGERPRINT.lower()) + self.assertTrue(key.private) + + def test_send_key_raises_key_not_found(self): + km = self._key_manager() + self.assertRaises( + KeyNotFound, + km.send_key, OpenPGPKey, send_private=False) + + def test_send_private_key_raises_key_not_found(self): + km = self._key_manager() + km._wrapper_map[OpenPGPKey].put_key_raw(PUBLIC_KEY) + self.assertRaises( + KeyNotFound, + km.send_key, OpenPGPKey, send_private=True, + password='123') + + def test_send_private_key_without_password_raises(self): + km = self._key_manager() + km._wrapper_map[OpenPGPKey].put_key_raw(PUBLIC_KEY) + self.assertRaises( + NoPasswordGiven, + km.send_key, OpenPGPKey, send_private=True) + + def test_send_public_key(self): + km = self._key_manager() + km._wrapper_map[OpenPGPKey].put_key_raw(PUBLIC_KEY) + km._fetcher.put = mock.Mock() + km.token = '123' + km.send_key(OpenPGPKey, send_private=False) + # setup args + data = { + 'address': km._address, + 'keys': [ + json.loads( + km.get_key( + km._address, OpenPGPKey).get_json()), + ] + } + url = km._nickserver_url + '/key/' + km._address + + km._fetcher.put.assert_called_once_with( + url, data=data, auth=(km._address, '123') + ) + + def test_fetch_keys_from_server(self): + km = self._key_manager() + # setup mock + + class Response(object): + status_code = 200 + headers = {'content-type': 'application/json'} + def json(self): + return {'address': 'anotheruser@leap.se', 'keys': []} + + km._fetcher.get = mock.Mock( + return_value=Response()) + # do the fetch + km.fetch_keys_from_server('anotheruser@leap.se') + # and verify the call + km._fetcher.get.assert_called_once_with( + km._nickserver_url + '/key/' + 'anotheruser@leap.se', + ) + + def test_refresh_keys(self): + # TODO: maybe we should not attempt to refresh our own public key? + km = self._key_manager() + km._wrapper_map[OpenPGPKey].put_key_raw(PUBLIC_KEY) + km.fetch_keys_from_server = mock.Mock(return_value=[]) + km.refresh_keys() + km.fetch_keys_from_server.assert_called_once_with( + 'leap@leap.se' + ) + # Key material for testing KEY_FINGERPRINT = "E36E738D69173C13D709E44F2F455E2824D18DDF" -- cgit v1.2.3 From be685b2eb0f6ccca4a6f44c89fbdd35c57ea0d2c Mon Sep 17 00:00:00 2001 From: Tomas Touceda Date: Tue, 7 May 2013 11:09:53 -0300 Subject: Add the proper URL for installing socketrpc --- pkg/requirements.pip | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/requirements.pip b/pkg/requirements.pip index e8f8e87..01fe3ea 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -1,8 +1,9 @@ jsonschema<=0.8 pyxdg protobuf -protobuf.socketrpc pyopenssl python-dateutil autopep8 -python-gnupg \ No newline at end of file +python-gnupg + +https://protobuf-socket-rpc.googlecode.com/files/protobuf.socketrpc-1.3.2.tar.gz \ No newline at end of file -- cgit v1.2.3 From c72aa2e8c356d57c272ce91e72417ee231edd57d Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 8 May 2013 01:47:14 +0900 Subject: version bump --- setup.py | 2 +- src/leap/common/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index d5323bc..1a3f2fd 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ setup( name='leap.common', # If you change version, do it also in # src/leap/common/__init__.py - version='0.2.1-dev2', + version='0.2.3-dev', url='https://leap.se/', license='GPLv3+', author='The LEAP Encryption Access Project', diff --git a/src/leap/common/__init__.py b/src/leap/common/__init__.py index a5ea197..5bcbb38 100644 --- a/src/leap/common/__init__.py +++ b/src/leap/common/__init__.py @@ -16,4 +16,4 @@ except ImportError: __all__ = ["certs", "check", "files", "events"] -__version__ = "0.2.1-dev2" +__version__ = "0.2.3-dev" -- cgit v1.2.3 From 8fae83a20504851845eeda5c089f2c53f8678eae Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 9 May 2013 15:56:04 -0300 Subject: Add sign/verify to keymanager's openpgp. --- src/leap/common/keymanager/openpgp.py | 47 ++++++++++++++++++++++++++++++-- src/leap/common/tests/test_keymanager.py | 30 +++++++++++++++++++- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/leap/common/keymanager/openpgp.py b/src/leap/common/keymanager/openpgp.py index e2ffe76..0fd314a 100644 --- a/src/leap/common/keymanager/openpgp.py +++ b/src/leap/common/keymanager/openpgp.py @@ -25,7 +25,7 @@ import re import tempfile import shutil -from leap.common.check import leap_assert +from leap.common.check import leap_assert, leap_assert_type from leap.common.keymanager.errors import ( KeyNotFound, KeyAlreadyExists, @@ -42,7 +42,7 @@ from leap.common.keymanager.gpg import GPGWrapper # -# Utility functions +# API functions # def encrypt_sym(data, passphrase): @@ -175,6 +175,49 @@ def is_encrypted_asym(data): return _safe_call(_is_encrypted_cb) +def sign(data, key): + """ + Sign C{data} with C{key}. + + @param data: The data to be signed. + @type data: str + @param key: The key to be used to sign. + @type key: OpenPGPKey + + @return: The ascii-armored signed data. + @rtype: str + """ + leap_assert_type(key, OpenPGPKey) + leap_assert(key.private == True) + + def _sign_cb(gpg): + return gpg.sign(data, keyid=key.key_id).data + + return _safe_call(_sign_cb, key.key_data) + +def verify(data, key): + """ + Verify signed C{data} with C{key}. + + @param data: The data to be verified. + @type data: str + @param key: The key to be used on verification. + @type key: OpenPGPKey + + @return: The ascii-armored signed data. + @rtype: str + """ + leap_assert_type(key, OpenPGPKey) + leap_assert(key.private == False) + + def _verify_cb(gpg): + return gpg.verify(data).valid + + return _safe_call(_verify_cb, key.key_data) + +# +# Helper functions +# def _build_key_from_gpg(address, key, key_data): """ diff --git a/src/leap/common/tests/test_keymanager.py b/src/leap/common/tests/test_keymanager.py index 1d7a382..d3dee40 100644 --- a/src/leap/common/tests/test_keymanager.py +++ b/src/leap/common/tests/test_keymanager.py @@ -169,7 +169,7 @@ class KeyManagerWithSoledadTestCase(BaseLeapTest): class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): - def test_openpgp_gen_key(self): + def _test_openpgp_gen_key(self): pgp = openpgp.OpenPGPScheme(self._soledad) self.assertRaises(KeyNotFound, pgp.get_key, 'user@leap.se') key = pgp.gen_key('user@leap.se') @@ -363,6 +363,34 @@ class KeyManagerKeyManagementTestCase( 'leap@leap.se' ) + def test_verify_with_private_raises(self): + km = self._key_manager() + km._wrapper_map[OpenPGPKey].put_key_raw(PRIVATE_KEY) + data = 'data' + privkey = km.get_key(ADDRESS, OpenPGPKey, private=True) + signed = openpgp.sign(data, privkey) + self.assertRaises( + AssertionError, + openpgp.verify, signed, privkey) + + def test_sign_with_public_raises(self): + km = self._key_manager() + km._wrapper_map[OpenPGPKey].put_key_raw(PUBLIC_KEY) + data = 'data' + pubkey = km.get_key(ADDRESS, OpenPGPKey, private=False) + self.assertRaises( + AssertionError, + openpgp.sign, data, pubkey) + + def test_sign_verify(self): + km = self._key_manager() + km._wrapper_map[OpenPGPKey].put_key_raw(PRIVATE_KEY) + data = 'data' + privkey = km.get_key(ADDRESS, OpenPGPKey, private=True) + signed = openpgp.sign(data, privkey) + pubkey = km.get_key(ADDRESS, OpenPGPKey, private=False) + self.assertTrue(openpgp.verify(signed, pubkey)) + # Key material for testing KEY_FINGERPRINT = "E36E738D69173C13D709E44F2F455E2824D18DDF" -- cgit v1.2.3 From 779afe2e830a48e91a9bda6dc8cd8b35ce555ed5 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sat, 11 May 2013 23:33:50 +0900 Subject: add repr method to encryption key --- src/leap/common/keymanager/keys.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/leap/common/keymanager/keys.py b/src/leap/common/keymanager/keys.py index 2e6bfe9..d98bd02 100644 --- a/src/leap/common/keymanager/keys.py +++ b/src/leap/common/keymanager/keys.py @@ -155,6 +155,16 @@ class EncryptionKey(object): 'tags': ['keymanager-key'], }) + def __repr__(self): + """ + Representation of this class + """ + return u"<%s 0x%s (%s - %s)>" % ( + self.__class__.__name__, + self.key_id, + self.address, + "priv" if self.private else "publ") + # # Encryption schemes -- cgit v1.2.3 From 23e435cf12b9bdd25410b3b8a48ced4168f872d2 Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 9 May 2013 17:53:07 -0300 Subject: Encrypt/decrypt can also sign/verify. Also Implement code review comments for openpgp sign. * Add assertions and exceptions to openpgp encrypt/decrypt/sign/verify methods. * Added comments where they will help. * Make code more clear by encapsulating more the access to GPG wrapper and removing concatenation of ascii armored keys. * Add verification of fingerprint of verifying key. * Shorten check for signing key id. --- src/leap/common/keymanager/errors.py | 28 ++++ src/leap/common/keymanager/openpgp.py | 279 +++++++++++++++++++++---------- src/leap/common/tests/test_keymanager.py | 246 ++++++++++++++++++++++----- 3 files changed, 427 insertions(+), 126 deletions(-) diff --git a/src/leap/common/keymanager/errors.py b/src/leap/common/keymanager/errors.py index 1cf506e..f712975 100644 --- a/src/leap/common/keymanager/errors.py +++ b/src/leap/common/keymanager/errors.py @@ -25,12 +25,14 @@ class KeyNotFound(Exception): """ Raised when key was no found on keyserver. """ + pass class KeyAlreadyExists(Exception): """ Raised when attempted to create a key that already exists. """ + pass class KeyAttributesDiffer(Exception): @@ -38,9 +40,35 @@ class KeyAttributesDiffer(Exception): Raised when trying to delete a key but the stored key differs from the key passed to the delete_key() method. """ + pass class NoPasswordGiven(Exception): """ Raised when trying to perform some action that needs a password without providing one. """ + pass + +class InvalidSignature(Exception): + """ + Raised when signature could not be verified. + """ + pass + +class EncryptionFailed(Exception): + """ + Raised upon failures of encryption. + """ + pass + +class DecryptionFailed(Exception): + """ + Raised upon failures of decryption. + """ + pass + +class SignFailed(Exception): + """ + Raised when failed to sign. + """ + pass diff --git a/src/leap/common/keymanager/openpgp.py b/src/leap/common/keymanager/openpgp.py index 0fd314a..0cb1308 100644 --- a/src/leap/common/keymanager/openpgp.py +++ b/src/leap/common/keymanager/openpgp.py @@ -29,7 +29,11 @@ from leap.common.check import leap_assert, leap_assert_type from leap.common.keymanager.errors import ( KeyNotFound, KeyAlreadyExists, - KeyAttributesDiffer + KeyAttributesDiffer, + InvalidSignature, + EncryptionFailed, + DecryptionFailed, + SignFailed, ) from leap.common.keymanager.keys import ( EncryptionKey, @@ -45,84 +49,163 @@ from leap.common.keymanager.gpg import GPGWrapper # API functions # -def encrypt_sym(data, passphrase): +def encrypt_sym(data, passphrase, sign=None): """ - Encrypt C{data} with C{passphrase}. + Encrypt C{data} with C{passphrase} and sign with C{sign} private key. @param data: The data to be encrypted. @type data: str @param passphrase: The passphrase used to encrypt C{data}. @type passphrase: str + @param sign: The private key used for signing. + @type sign: OpenPGPKey @return: The encrypted data. @rtype: str """ + leap_assert_type(passphrase, str) + if sign is not None: + leap_assert_type(sign, OpenPGPKey) + leap_assert(sign.private == True) def _encrypt_cb(gpg): - return gpg.encrypt( - data, None, passphrase=passphrase, symmetric=True).data - - return _safe_call(_encrypt_cb) - - -def decrypt_sym(data, passphrase): + result = gpg.encrypt( + data, None, + sign=sign.key_id if sign else None, + passphrase=passphrase, symmetric=True) + # Here we cannot assert for correctness of sig because the sig is in + # the ciphertext. + # result.ok - (bool) indicates if the operation succeeded + # result.data - (bool) contains the result of the operation + if result.ok is False: + raise EncryptionFailed('Failed to encrypt: %s' % result.stderr) + return result.data + + return _safe_call(_encrypt_cb, [sign]) + + +def decrypt_sym(data, passphrase, verify=None): """ - Decrypt C{data} with C{passphrase}. + Decrypt C{data} with C{passphrase} and verify with C{verify} public + key. @param data: The data to be decrypted. @type data: str @param passphrase: The passphrase used to decrypt C{data}. @type passphrase: str + @param verify: The key used to verify a signature. + @type verify: OpenPGPKey @return: The decrypted data. @rtype: str + + @raise InvalidSignature: Raised if unable to verify the signature with + C{verify} key. """ + leap_assert_type(passphrase, str) + if verify is not None: + leap_assert_type(verify, OpenPGPKey) + leap_assert(verify.private == False) def _decrypt_cb(gpg): - return gpg.decrypt(data, passphrase=passphrase).data - - return _safe_call(_decrypt_cb) - - -def encrypt_asym(data, key): + result = gpg.decrypt(data, passphrase=passphrase) + # result.ok - (bool) indicates if the operation succeeded + # result.valid - (bool) indicates if the signature was verified + # result.data - (bool) contains the result of the operation + # result.pubkey_fingerpring - (str) contains the fingerprint of the + # public key that signed this data. + if result.ok is False: + raise DecryptionFailed('Failed to decrypt: %s', result.stderr) + if verify is not None: + if result.valid is False or \ + verify.fingerprint != result.pubkey_fingerprint: + raise InvalidSignature( + 'Failed to verify signature with key %s: %s' % + (verify.key_id, result.stderr)) + return result.data + + return _safe_call(_decrypt_cb, [verify]) + + +def encrypt_asym(data, pubkey, sign=None): """ - Encrypt C{data} using public @{key}. + Encrypt C{data} using public @{key} and sign with C{sign} key. @param data: The data to be encrypted. @type data: str - @param key: The key used to encrypt. - @type key: OpenPGPKey + @param pubkey: The key used to encrypt. + @type pubkey: OpenPGPKey + @param sign: The key used for signing. + @type sign: OpenPGPKey @return: The encrypted data. @rtype: str """ - leap_assert(key.private is False, 'Key is not public.') + leap_assert_type(pubkey, OpenPGPKey) + leap_assert(pubkey.private is False, 'Key is not public.') + if sign is not None: + leap_assert_type(sign, OpenPGPKey) + leap_assert(sign.private == True) def _encrypt_cb(gpg): - return gpg.encrypt( - data, key.fingerprint, symmetric=False).data - - return _safe_call(_encrypt_cb, key.key_data) - - -def decrypt_asym(data, key): + result = gpg.encrypt( + data, pubkey.fingerprint, + sign=sign.key_id if sign else None, + symmetric=False) + # Here we cannot assert for correctness of sig because the sig is in + # the ciphertext. + # result.ok - (bool) indicates if the operation succeeded + # result.data - (bool) contains the result of the operation + if result.ok is False: + raise EncryptionFailed( + 'Failed to encrypt with key %s: %s' % + (pubkey.key_id, result.stderr)) + return result.data + + return _safe_call(_encrypt_cb, [pubkey, sign]) + + +def decrypt_asym(data, privkey, verify=None): """ - Decrypt C{data} using private @{key}. + Decrypt C{data} using private @{key} and verify with C{verify} key. @param data: The data to be decrypted. @type data: str - @param key: The key used to decrypt. - @type key: OpenPGPKey + @param privkey: The key used to decrypt. + @type privkey: OpenPGPKey + @param verify: The key used to verify a signature. + @type verify: OpenPGPKey @return: The decrypted data. @rtype: str + + @raise InvalidSignature: Raised if unable to verify the signature with + C{verify} key. """ - leap_assert(key.private is True, 'Key is not private.') + leap_assert(privkey.private is True, 'Key is not private.') + if verify is not None: + leap_assert_type(verify, OpenPGPKey) + leap_assert(verify.private == False) def _decrypt_cb(gpg): - return gpg.decrypt(data).data - - return _safe_call(_decrypt_cb, key.key_data) + result = gpg.decrypt(data) + # result.ok - (bool) indicates if the operation succeeded + # result.valid - (bool) indicates if the signature was verified + # result.data - (bool) contains the result of the operation + # result.pubkey_fingerpring - (str) contains the fingerprint of the + # public key that signed this data. + if result.ok is False: + raise DecryptionFailed('Failed to decrypt with key %s: %s' % + (privkey.key_id, result.stderr)) + if verify is not None: + if result.valid is False or \ + verify.fingerprint != result.pubkey_fingerprint: + raise InvalidSignature( + 'Failed to verify signature with key %s: %s' % + (verify.key_id, result.stderr)) + return result.data + + return _safe_call(_decrypt_cb, [privkey, verify]) def is_encrypted(data): @@ -175,45 +258,61 @@ def is_encrypted_asym(data): return _safe_call(_is_encrypted_cb) -def sign(data, key): +def sign(data, privkey): """ - Sign C{data} with C{key}. + Sign C{data} with C{privkey}. @param data: The data to be signed. @type data: str - @param key: The key to be used to sign. - @type key: OpenPGPKey + @param privkey: The private key to be used to sign. + @type privkey: OpenPGPKey @return: The ascii-armored signed data. @rtype: str """ - leap_assert_type(key, OpenPGPKey) - leap_assert(key.private == True) + leap_assert_type(privkey, OpenPGPKey) + leap_assert(privkey.private == True) def _sign_cb(gpg): - return gpg.sign(data, keyid=key.key_id).data + result = gpg.sign(data, keyid=privkey.key_id) + # result.fingerprint - contains the fingerprint of the key used to + # sign. + if result.fingerprint is None: + raise SignFailed( + 'Failed to sign with key %s: %s' % + (privkey.key_id, result.stderr)) + leap_assert( + result.fingerprint == privkey.fingerprint, + 'Signature and private key fingerprints mismatch: %s != %s' % + (result.fingerprint, privkey.fingerprint)) + return result.data - return _safe_call(_sign_cb, key.key_data) + return _safe_call(_sign_cb, [privkey]) -def verify(data, key): +def verify(data, pubkey): """ - Verify signed C{data} with C{key}. + Verify signed C{data} with C{pubkey}. @param data: The data to be verified. @type data: str - @param key: The key to be used on verification. - @type key: OpenPGPKey + @param pubkey: The public key to be used on verification. + @type pubkey: OpenPGPKey @return: The ascii-armored signed data. @rtype: str """ - leap_assert_type(key, OpenPGPKey) - leap_assert(key.private == False) + leap_assert_type(pubkey, OpenPGPKey) + leap_assert(pubkey.private == False) def _verify_cb(gpg): - return gpg.verify(data).valid + result = gpg.verify(data) + if result.valid is False or \ + result.fingerprint != pubkey.fingerprint: + raise InvalidSignature( + 'Failed to verify signature with key %s.' % pubkey.key_id) + return True - return _safe_call(_verify_cb, key.key_data) + return _safe_call(_verify_cb, [pubkey]) # # Helper functions @@ -248,35 +347,48 @@ def _build_key_from_gpg(address, key, key_data): ) -def _build_unitary_gpgwrapper(key_data=None): +def _build_keyring(keys=[]): """ - Return a temporary GPG wrapper keyring containing exactly zero or one - keys. - Temporary unitary keyrings allow the to use GPG's facilities for exactly - one key. This function creates an empty temporary keyring and imports - C{key_data} if it is not None. + Create an empty GPG keyring and import C{keys} into it. + + @param keys: List of keys to add to the keyring. + @type keys: list of OpenPGPKey - @param key_data: ASCII armored key data. - @type key_data: str @return: A GPG wrapper with a unitary keyring. @rtype: gnupg.GPG """ + privkeys = filter(lambda key: key.private == True, keys) + pubkeys = filter(lambda key: key.private == False, keys) + # here we filter out public keys that have a correspondent private key in + # the list because the private key_data by itself is enough to also have + # the public key in the keyring, and we want to count the keys afterwards. + privaddrs = map(lambda privkey: privkey.address, privkeys) + pubkeys = filter(lambda pubkey: pubkey.address not in privaddrs, pubkeys) + # create temporary dir for temporary gpg keyring tmpdir = tempfile.mkdtemp() gpg = GPGWrapper(gnupghome=tmpdir) leap_assert(len(gpg.list_keys()) is 0, 'Keyring not empty.') - if key_data: - gpg.import_keys(key_data) - leap_assert( - len(gpg.list_keys()) is 1, - 'Unitary keyring has wrong number of keys: %d.' - % len(gpg.list_keys())) + # import keys into the keyring + gpg.import_keys( + reduce( + lambda x, y: x+y, + [key.key_data for key in pubkeys+privkeys], '')) + # assert the number of keys in the keyring + leap_assert( + len(gpg.list_keys()) == len(pubkeys)+len(privkeys), + 'Wrong number of public keys in keyring: %d, should be %d)' % + (len(gpg.list_keys()), len(pubkeys)+len(privkeys))) + leap_assert( + len(gpg.list_keys(secret=True)) == len(privkeys), + 'Wrong number of private keys in keyring: %d, should be %d)' % + (len(gpg.list_keys(secret=True)), len(privkeys))) return gpg -def _destroy_unitary_gpgwrapper(gpg): +def _destroy_keyring(gpg): """ - Securely erase a unitary keyring. + Securely erase a keyring. @param gpg: A GPG wrapper instance. @type gpg: gnupg.GPG @@ -292,23 +404,21 @@ def _destroy_unitary_gpgwrapper(gpg): shutil.rmtree(gpg.gnupghome) -def _safe_call(callback, key_data=None, **kwargs): +def _safe_call(callback, keys=[]): """ - Run C{callback} in an unitary keyring containing C{key_data}. + Run C{callback} over a keyring containing C{keys}. @param callback: Function whose first argument is the gpg keyring. @type callback: function(gnupg.GPG) - @param key_data: ASCII armored key data. - @type key_data: str - @param **kwargs: Other eventual parameters for the callback. - @type **kwargs: **dict + @param keys: List of keys to add to the keyring. + @type keys: list of OpenPGPKey @return: The results of the callback. @rtype: str or bool """ - gpg = _build_unitary_gpgwrapper(key_data) - val = callback(gpg, **kwargs) - _destroy_unitary_gpgwrapper(gpg) + gpg = _build_keyring(filter(lambda key: key is not None, keys)) + val = callback(gpg) + _destroy_keyring(gpg) return val @@ -404,18 +514,17 @@ class OpenPGPScheme(EncryptionScheme): raise KeyNotFound(address) return build_key_from_dict(OpenPGPKey, address, doc.content) - def put_key_raw(self, data): + def put_ascii_key(self, key_data): """ - Put key contained in raw C{data} in local storage. + Put key contained in ascii-armored C{key_data} in local storage. - @param data: The key data to be stored. - @type data: str + @param key_data: The key data to be stored. + @type key_data: str """ - # TODO: add more checks for correct key data. - leap_assert(data is not None, 'Data does not represent a key.') - - def _put_key_raw_cb(gpg): + leap_assert_type(key_data, str) + def _put_ascii_key_cb(gpg): + gpg.import_keys(key_data) privkey = None pubkey = None try: @@ -449,7 +558,7 @@ class OpenPGPScheme(EncryptionScheme): gpg.export_keys(pubkey['fingerprint'], secret=False)) self.put_key(openpgp_pubkey) - _safe_call(_put_key_raw_cb, data) + _safe_call(_put_ascii_key_cb) def put_key(self, key): """ diff --git a/src/leap/common/tests/test_keymanager.py b/src/leap/common/tests/test_keymanager.py index d3dee40..e52766a 100644 --- a/src/leap/common/tests/test_keymanager.py +++ b/src/leap/common/tests/test_keymanager.py @@ -51,6 +51,7 @@ from leap.common.keymanager import errors ADDRESS = 'leap@leap.se' +ADDRESS_2 = 'anotheruser@leap.se' class KeyManagerUtilTestCase(BaseLeapTest): @@ -182,15 +183,15 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_openpgp_put_delete_key(self): pgp = openpgp.OpenPGPScheme(self._soledad) self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - pgp.put_key_raw(PUBLIC_KEY) + pgp.put_ascii_key(PUBLIC_KEY) key = pgp.get_key(ADDRESS, private=False) pgp.delete_key(key) self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - def test_openpgp_put_key_raw(self): + def test_openpgp_put_ascii_key(self): pgp = openpgp.OpenPGPScheme(self._soledad) self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - pgp.put_key_raw(PUBLIC_KEY) + pgp.put_ascii_key(PUBLIC_KEY) key = pgp.get_key(ADDRESS, private=False) self.assertIsInstance(key, openpgp.OpenPGPKey) self.assertEqual( @@ -203,7 +204,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_get_public_key(self): pgp = openpgp.OpenPGPScheme(self._soledad) self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - pgp.put_key_raw(PUBLIC_KEY) + pgp.put_ascii_key(PUBLIC_KEY) self.assertRaises( KeyNotFound, pgp.get_key, ADDRESS, private=True) key = pgp.get_key(ADDRESS, private=False) @@ -216,7 +217,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_openpgp_encrypt_decrypt_asym(self): # encrypt pgp = openpgp.OpenPGPScheme(self._soledad) - pgp.put_key_raw(PUBLIC_KEY) + pgp.put_ascii_key(PUBLIC_KEY) pubkey = pgp.get_key(ADDRESS, private=False) cyphertext = openpgp.encrypt_asym('data', pubkey) # assert @@ -229,7 +230,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): # decrypt self.assertRaises( KeyNotFound, pgp.get_key, ADDRESS, private=True) - pgp.put_key_raw(PRIVATE_KEY) + pgp.put_ascii_key(PRIVATE_KEY) privkey = pgp.get_key(ADDRESS, private=True) plaintext = openpgp.decrypt_asym(cyphertext, privkey) pgp.delete_key(pubkey) @@ -250,13 +251,146 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): plaintext = openpgp.decrypt_sym(cyphertext, 'pass') self.assertEqual('data', plaintext) + def test_verify_with_private_raises(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_ascii_key(PRIVATE_KEY) + data = 'data' + privkey = pgp.get_key(ADDRESS, private=True) + signed = openpgp.sign(data, privkey) + self.assertRaises( + AssertionError, + openpgp.verify, signed, privkey) + + def test_sign_with_public_raises(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_ascii_key(PUBLIC_KEY) + data = 'data' + pubkey = pgp.get_key(ADDRESS, private=False) + self.assertRaises( + AssertionError, + openpgp.sign, data, pubkey) + + def test_verify_with_wrong_key_raises(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_ascii_key(PRIVATE_KEY) + data = 'data' + privkey = pgp.get_key(ADDRESS, private=True) + signed = openpgp.sign(data, privkey) + pgp.put_ascii_key(PUBLIC_KEY_2) + wrongkey = pgp.get_key(ADDRESS_2) + self.assertRaises( + errors.InvalidSignature, + openpgp.verify, signed, wrongkey) + + def test_encrypt_asym_sign_with_public_raises(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_ascii_key(PRIVATE_KEY) + data = 'data' + privkey = pgp.get_key(ADDRESS, private=True) + pubkey = pgp.get_key(ADDRESS, private=False) + self.assertRaises( + AssertionError, + openpgp.encrypt_asym, data, privkey, sign=pubkey) + + def test_encrypt_sym_sign_with_public_raises(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_ascii_key(PUBLIC_KEY) + data = 'data' + pubkey = pgp.get_key(ADDRESS, private=False) + self.assertRaises( + AssertionError, + openpgp.encrypt_sym, data, '123', sign=pubkey) + + def test_decrypt_asym_verify_with_private_raises(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_ascii_key(PRIVATE_KEY) + data = 'data' + privkey = pgp.get_key(ADDRESS, private=True) + pubkey = pgp.get_key(ADDRESS, private=False) + encrypted_and_signed = openpgp.encrypt_asym( + data, pubkey, sign=privkey) + self.assertRaises( + AssertionError, + openpgp.decrypt_asym, + encrypted_and_signed, privkey, verify=privkey) + + def test_decrypt_asym_verify_with_wrong_key_raises(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_ascii_key(PRIVATE_KEY) + data = 'data' + privkey = pgp.get_key(ADDRESS, private=True) + pubkey = pgp.get_key(ADDRESS, private=False) + encrypted_and_signed = openpgp.encrypt_asym(data, pubkey, sign=privkey) + pgp.put_ascii_key(PUBLIC_KEY_2) + wrongkey = pgp.get_key('anotheruser@leap.se') + self.assertRaises( + errors.InvalidSignature, + openpgp.verify, encrypted_and_signed, wrongkey) + + def test_decrypt_sym_verify_with_private_raises(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_ascii_key(PRIVATE_KEY) + data = 'data' + privkey = pgp.get_key(ADDRESS, private=True) + encrypted_and_signed = openpgp.encrypt_sym(data, '123', sign=privkey) + self.assertRaises( + AssertionError, + openpgp.decrypt_sym, + encrypted_and_signed, 'decrypt', verify=privkey) + + def test_decrypt_sym_verify_with_private_raises(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_ascii_key(PRIVATE_KEY) + data = 'data' + privkey = pgp.get_key(ADDRESS, private=True) + encrypted_and_signed = openpgp.encrypt_sym(data, '123', sign=privkey) + pgp.put_ascii_key(PUBLIC_KEY_2) + wrongkey = pgp.get_key('anotheruser@leap.se') + self.assertRaises( + errors.InvalidSignature, + openpgp.verify, encrypted_and_signed, wrongkey) + + def test_sign_verify(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_ascii_key(PRIVATE_KEY) + data = 'data' + privkey = pgp.get_key(ADDRESS, private=True) + signed = openpgp.sign(data, privkey) + pubkey = pgp.get_key(ADDRESS, private=False) + self.assertTrue(openpgp.verify(signed, pubkey)) + + def test_encrypt_asym_sign_decrypt_verify(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_ascii_key(PRIVATE_KEY) + pubkey = pgp.get_key(ADDRESS, private=False) + privkey = pgp.get_key(ADDRESS, private=True) + pgp.put_ascii_key(PRIVATE_KEY_2) + pubkey2 = pgp.get_key(ADDRESS_2, private=False) + privkey2 = pgp.get_key(ADDRESS_2, private=True) + data = 'data' + encrypted_and_signed = openpgp.encrypt_asym(data, pubkey2, sign=privkey) + res = openpgp.decrypt_asym( + encrypted_and_signed, privkey2, verify=pubkey) + self.assertTrue(data, res) + + def test_encrypt_sym_sign_decrypt_verify(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_ascii_key(PRIVATE_KEY) + data = 'data' + privkey = pgp.get_key(ADDRESS, private=True) + pubkey = pgp.get_key(ADDRESS, private=False) + encrypted_and_signed = openpgp.encrypt_sym(data, '123', sign=privkey) + res = openpgp.decrypt_sym( + encrypted_and_signed, '123', verify=pubkey) + self.assertEqual(data, res) + class KeyManagerKeyManagementTestCase( KeyManagerWithSoledadTestCase): def test_get_all_keys_in_db(self): km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_key_raw(PRIVATE_KEY) + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) # get public keys keys = km.get_all_keys_in_local_db(False) self.assertEqual(len(keys), 1, 'Wrong number of keys') @@ -270,7 +404,7 @@ class KeyManagerKeyManagementTestCase( def test_get_public_key(self): km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_key_raw(PRIVATE_KEY) + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) # get the key key = km.get_key(ADDRESS, OpenPGPKey, private=False, fetch_remote=False) @@ -282,7 +416,7 @@ class KeyManagerKeyManagementTestCase( def test_get_private_key(self): km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_key_raw(PRIVATE_KEY) + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) # get the key key = km.get_key(ADDRESS, OpenPGPKey, private=True, fetch_remote=False) @@ -300,7 +434,7 @@ class KeyManagerKeyManagementTestCase( def test_send_private_key_raises_key_not_found(self): km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_key_raw(PUBLIC_KEY) + km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY) self.assertRaises( KeyNotFound, km.send_key, OpenPGPKey, send_private=True, @@ -308,14 +442,14 @@ class KeyManagerKeyManagementTestCase( def test_send_private_key_without_password_raises(self): km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_key_raw(PUBLIC_KEY) + km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY) self.assertRaises( NoPasswordGiven, km.send_key, OpenPGPKey, send_private=True) def test_send_public_key(self): km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_key_raw(PUBLIC_KEY) + km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY) km._fetcher.put = mock.Mock() km.token = '123' km.send_key(OpenPGPKey, send_private=False) @@ -356,41 +490,13 @@ class KeyManagerKeyManagementTestCase( def test_refresh_keys(self): # TODO: maybe we should not attempt to refresh our own public key? km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_key_raw(PUBLIC_KEY) + km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY) km.fetch_keys_from_server = mock.Mock(return_value=[]) km.refresh_keys() km.fetch_keys_from_server.assert_called_once_with( 'leap@leap.se' ) - def test_verify_with_private_raises(self): - km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_key_raw(PRIVATE_KEY) - data = 'data' - privkey = km.get_key(ADDRESS, OpenPGPKey, private=True) - signed = openpgp.sign(data, privkey) - self.assertRaises( - AssertionError, - openpgp.verify, signed, privkey) - - def test_sign_with_public_raises(self): - km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_key_raw(PUBLIC_KEY) - data = 'data' - pubkey = km.get_key(ADDRESS, OpenPGPKey, private=False) - self.assertRaises( - AssertionError, - openpgp.sign, data, pubkey) - - def test_sign_verify(self): - km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_key_raw(PRIVATE_KEY) - data = 'data' - privkey = km.get_key(ADDRESS, OpenPGPKey, private=True) - signed = openpgp.sign(data, privkey) - pubkey = km.get_key(ADDRESS, OpenPGPKey, private=False) - self.assertTrue(openpgp.verify(signed, pubkey)) - # Key material for testing KEY_FINGERPRINT = "E36E738D69173C13D709E44F2F455E2824D18DDF" @@ -554,3 +660,61 @@ RZXoH+FTg9UAW87eqU610npOkT6cRaBxaMK/mDtGNdc= =JTFu -----END PGP PRIVATE KEY BLOCK----- """ + +PUBLIC_KEY_2 = """ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +mI0EUYwJXgEEAMbTKHuPJ5/Gk34l9Z06f+0WCXTDXdte1UBoDtZ1erAbudgC4MOR +gquKqoj3Hhw0/ILqJ88GcOJmKK/bEoIAuKaqlzDF7UAYpOsPZZYmtRfPC2pTCnXq +Z1vdeqLwTbUspqXflkCkFtfhGKMq5rH8GV5a3tXZkRWZhdNwhVXZagC3ABEBAAG0 +IWFub3RoZXJ1c2VyIDxhbm90aGVydXNlckBsZWFwLnNlPoi4BBMBAgAiBQJRjAle +AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRB/nfpof+5XWotuA/4tLN4E +gUr7IfLy2HkHAxzw7A4rqfMN92DIM9mZrDGaWRrOn3aVF7VU1UG7MDkHfPvp/cFw +ezoCw4s4IoHVc/pVlOkcHSyt4/Rfh248tYEJmFCJXGHpkK83VIKYJAithNccJ6Q4 +JE/o06Mtf4uh/cA1HUL4a4ceqUhtpLJULLeKo7iNBFGMCV4BBADsyQI7GR0wSAxz +VayLjuPzgT+bjbFeymIhjuxKIEwnIKwYkovztW+4bbOcQs785k3Lp6RzvigTpQQt +Z/hwcLOqZbZw8t/24+D+Pq9mMP2uUvCFFqLlVvA6D3vKSQ/XNN+YB919WQ04jh63 +yuRe94WenT1RJd6xU1aaUff4rKizuQARAQABiJ8EGAECAAkFAlGMCV4CGwwACgkQ +f536aH/uV1rPZQQAqCzRysOlu8ez7PuiBD4SebgRqWlxa1TF1ujzfLmuPivROZ2X +Kw5aQstxgGSjoB7tac49s0huh4X8XK+BtJBfU84JS8Jc2satlfwoyZ35LH6sDZck +I+RS/3we6zpMfHs3vvp9xgca6ZupQxivGtxlJs294TpJorx+mFFqbV17AzQ= +=Thdu +-----END PGP PUBLIC KEY BLOCK----- +""" + +PRIVATE_KEY_2 = """ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +lQHYBFGMCV4BBADG0yh7jyefxpN+JfWdOn/tFgl0w13bXtVAaA7WdXqwG7nYAuDD +kYKriqqI9x4cNPyC6ifPBnDiZiiv2xKCALimqpcwxe1AGKTrD2WWJrUXzwtqUwp1 +6mdb3Xqi8E21LKal35ZApBbX4RijKuax/BleWt7V2ZEVmYXTcIVV2WoAtwARAQAB +AAP7BLuSAx7tOohnimEs74ks8l/L6dOcsFQZj2bqs4AoY3jFe7bV0tHr4llypb/8 +H3/DYvpf6DWnCjyUS1tTnXSW8JXtx01BUKaAufSmMNg9blKV6GGHlT/Whe9uVyks +7XHk/+9mebVMNJ/kNlqq2k+uWqJohzC8WWLRK+d1tBeqDsECANZmzltPaqUsGV5X +C3zszE3tUBgptV/mKnBtopKi+VH+t7K6fudGcG+bAcZDUoH/QVde52mIIjjIdLje +uajJuHUCAO1mqh+vPoGv4eBLV7iBo3XrunyGXiys4a39eomhxTy3YktQanjjx+ty +GltAGCs5PbWGO6/IRjjvd46wh53kzvsCAO0J97gsWhzLuFnkxFAJSPk7RRlyl7lI +1XS/x0Og6j9XHCyY1OYkfBm0to3UlCfkgirzCYlTYObCofzdKFIPDmSqHbQhYW5v +dGhlcnVzZXIgPGFub3RoZXJ1c2VyQGxlYXAuc2U+iLgEEwECACIFAlGMCV4CGwMG +CwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEH+d+mh/7ldai24D/i0s3gSBSvsh +8vLYeQcDHPDsDiup8w33YMgz2ZmsMZpZGs6fdpUXtVTVQbswOQd8++n9wXB7OgLD +izgigdVz+lWU6RwdLK3j9F+Hbjy1gQmYUIlcYemQrzdUgpgkCK2E1xwnpDgkT+jT +oy1/i6H9wDUdQvhrhx6pSG2kslQst4qjnQHYBFGMCV4BBADsyQI7GR0wSAxzVayL +juPzgT+bjbFeymIhjuxKIEwnIKwYkovztW+4bbOcQs785k3Lp6RzvigTpQQtZ/hw +cLOqZbZw8t/24+D+Pq9mMP2uUvCFFqLlVvA6D3vKSQ/XNN+YB919WQ04jh63yuRe +94WenT1RJd6xU1aaUff4rKizuQARAQABAAP9EyElqJ3dq3EErXwwT4mMnbd1SrVC +rUJrNWQZL59mm5oigS00uIyR0SvusOr+UzTtd8ysRuwHy5d/LAZsbjQStaOMBILx +77TJveOel0a1QK0YSMF2ywZMCKvquvjli4hAtWYz/EwfuzQN3t23jc5ny+GqmqD2 +3FUxLJosFUfLNmECAO9KhVmJi+L9dswIs+2Dkjd1eiRQzNOEVffvYkGYZyKxNiXF +UA5kvyZcB4iAN9sWCybE4WHZ9jd4myGB0MPDGxkCAP1RsXJbbuD6zS7BXe5gwunO +2q4q7ptdSl/sJYQuTe1KNP5d/uGsvlcFfsYjpsopasPjFBIncc/2QThMKlhoEaEB +/0mVAxpT6SrEvUbJ18z7kna24SgMPr3OnPMxPGfvNLJY/Xv/A17YfoqjmByCvsKE +JCDjopXtmbcrZyoEZbEht9mko4ifBBgBAgAJBQJRjAleAhsMAAoJEH+d+mh/7lda +z2UEAKgs0crDpbvHs+z7ogQ+Enm4EalpcWtUxdbo83y5rj4r0TmdlysOWkLLcYBk +o6Ae7WnOPbNIboeF/FyvgbSQX1POCUvCXNrGrZX8KMmd+Sx+rA2XJCPkUv98Hus6 +THx7N776fcYHGumbqUMYrxrcZSbNveE6SaK8fphRam1dewM0 +=a5gs +-----END PGP PRIVATE KEY BLOCK----- +""" -- cgit v1.2.3 From 8aa61059ecab25888d97aa26bcd59c0ffd09b9e9 Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 9 May 2013 19:26:57 -0300 Subject: Add changes file. --- changes/feature_openpgp-sign-verify | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/feature_openpgp-sign-verify diff --git a/changes/feature_openpgp-sign-verify b/changes/feature_openpgp-sign-verify new file mode 100644 index 0000000..9422edc --- /dev/null +++ b/changes/feature_openpgp-sign-verify @@ -0,0 +1 @@ + o Add OpenPGP sign/verify -- cgit v1.2.3 From 42d46aae15fcdda803b4dc8e3e2f37f6e695f09d Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 14 May 2013 15:18:21 -0300 Subject: Fix dependency link for protobuf.socketrpc dependency. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1a3f2fd..410fc6d 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ requirements = [ dependency_links = [ - "https://protobuf-socket-rpc.googlecode.com/files/protobuf.socketrpc-1.3.2.tar.gz" + "https://protobuf-socket-rpc.googlecode.com/files/protobuf.socketrpc-1.3.2.tar.gz#egg=protobuf.socketrpc" ] -- cgit v1.2.3 From adc5bb1c8e6ff5bada17e27ed772dee1ba424b28 Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 14 May 2013 15:53:04 -0300 Subject: Add dependency on soledad and install tests. --- setup.py | 11 ++++++++++- src/leap/common/tests/test_keymanager.py | 20 +++++++++----------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/setup.py b/setup.py index 410fc6d..ad44633 100644 --- a/setup.py +++ b/setup.py @@ -27,6 +27,7 @@ requirements = [ 'protobuf.socketrpc', "PyOpenSSL", "python-dateutil", + "leap.soledad", ] @@ -34,6 +35,10 @@ dependency_links = [ "https://protobuf-socket-rpc.googlecode.com/files/protobuf.socketrpc-1.3.2.tar.gz#egg=protobuf.socketrpc" ] +tests_requirements = [ + 'mock', +] + # XXX add classifiers, docs @@ -52,8 +57,12 @@ setup( ), namespace_packages=["leap"], package_dir={'': 'src'}, - packages=find_packages('src', exclude=['leap.common.tests']), + # For now, we do not exclude tests because of the circular dependency + # between leap.common and leap.soledad. + #packages=find_packages('src', exclude=['leap.common.tests']), + packages=find_packages('src'), test_suite='leap.common.tests', install_requires=requirements, dependency_links=dependency_links, + tests_require=tests_requirements, ) diff --git a/src/leap/common/tests/test_keymanager.py b/src/leap/common/tests/test_keymanager.py index e52766a..68a30e7 100644 --- a/src/leap/common/tests/test_keymanager.py +++ b/src/leap/common/tests/test_keymanager.py @@ -21,7 +21,7 @@ Tests for the Key Manager. """ -import mock +from mock import Mock try: import simplejson as json except ImportError: @@ -141,6 +141,11 @@ class KeyManagerUtilTestCase(BaseLeapTest): class KeyManagerWithSoledadTestCase(BaseLeapTest): def setUp(self): + # mock key fetching and storing so Soledad doesn't fail when trying to + # reach the server. + Soledad._fetch_keys_from_shared_db = Mock(return_value=None) + Soledad._assert_keys_in_shared_db = Mock(return_value=None) + self._soledad = Soledad( "leap@leap.se", "123456", @@ -148,14 +153,7 @@ class KeyManagerWithSoledadTestCase(BaseLeapTest): local_db_path=self.tempdir+"/soledad.u1db", server_url='', cert_file=None, - bootstrap=False, ) - # initialize solead by hand for testing purposes - self._soledad._init_dirs() - self._soledad._crypto = SoledadCrypto(self._soledad) - self._soledad._shared_db = None - self._soledad._init_keys() - self._soledad._init_db() def tearDown(self): km = self._key_manager() @@ -450,7 +448,7 @@ class KeyManagerKeyManagementTestCase( def test_send_public_key(self): km = self._key_manager() km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY) - km._fetcher.put = mock.Mock() + km._fetcher.put = Mock() km.token = '123' km.send_key(OpenPGPKey, send_private=False) # setup args @@ -478,7 +476,7 @@ class KeyManagerKeyManagementTestCase( def json(self): return {'address': 'anotheruser@leap.se', 'keys': []} - km._fetcher.get = mock.Mock( + km._fetcher.get = Mock( return_value=Response()) # do the fetch km.fetch_keys_from_server('anotheruser@leap.se') @@ -491,7 +489,7 @@ class KeyManagerKeyManagementTestCase( # TODO: maybe we should not attempt to refresh our own public key? km = self._key_manager() km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY) - km.fetch_keys_from_server = mock.Mock(return_value=[]) + km.fetch_keys_from_server = Mock(return_value=[]) km.refresh_keys() km.fetch_keys_from_server.assert_called_once_with( 'leap@leap.se' -- cgit v1.2.3 From d4eaa23280da258eb43baf345b1118a91c6d47fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 15 May 2013 16:43:41 -0300 Subject: Bump leap.common version for tagging --- setup.py | 2 +- src/leap/common/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index ad44633..112736e 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ setup( name='leap.common', # If you change version, do it also in # src/leap/common/__init__.py - version='0.2.3-dev', + version='0.2.4', url='https://leap.se/', license='GPLv3+', author='The LEAP Encryption Access Project', diff --git a/src/leap/common/__init__.py b/src/leap/common/__init__.py index 5bcbb38..2821f12 100644 --- a/src/leap/common/__init__.py +++ b/src/leap/common/__init__.py @@ -16,4 +16,4 @@ except ImportError: __all__ = ["certs", "check", "files", "events"] -__version__ = "0.2.3-dev" +__version__ = "0.2.4" -- cgit v1.2.3 From 92ab86bf33b57e40745d4af80e4bc2aa7fc4ac89 Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 16 May 2013 12:09:27 -0300 Subject: Fix pep8 style. --- src/leap/common/events/component.py | 7 ++++--- src/leap/common/keymanager/__init__.py | 2 +- src/leap/common/keymanager/errors.py | 5 +++++ src/leap/common/keymanager/openpgp.py | 19 +++++++++++-------- src/leap/common/tests/test_keymanager.py | 12 +++++++----- 5 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/leap/common/events/component.py b/src/leap/common/events/component.py index 0cf0e38..98a97bc 100644 --- a/src/leap/common/events/component.py +++ b/src/leap/common/events/component.py @@ -134,9 +134,10 @@ def register(signal, callback, uid=None, replace=False, reqcbk=None, request.mac = "" service = RpcService(proto.EventsServerService_Stub, server.SERVER_PORT, 'localhost') - logger.info("Sending registration request to server on port %s: %s", - server.SERVER_PORT, - str(request)) + logger.info( + "Sending registration request to server on port %s: %s", + server.SERVER_PORT, + str(request)) return service.register(request, callback=reqcbk, timeout=timeout) diff --git a/src/leap/common/keymanager/__init__.py b/src/leap/common/keymanager/__init__.py index d6dbb8a..ab28510 100644 --- a/src/leap/common/keymanager/__init__.py +++ b/src/leap/common/keymanager/__init__.py @@ -116,7 +116,7 @@ class KeyManager(object): leap_assert(response.status_code == 200, 'Invalid response.') leap_assert( response.headers['content-type'].startswith('application/json') - is True, + is True, 'Content-type is not JSON.') return response.json() diff --git a/src/leap/common/keymanager/errors.py b/src/leap/common/keymanager/errors.py index f712975..d6802f0 100644 --- a/src/leap/common/keymanager/errors.py +++ b/src/leap/common/keymanager/errors.py @@ -42,6 +42,7 @@ class KeyAttributesDiffer(Exception): """ pass + class NoPasswordGiven(Exception): """ Raised when trying to perform some action that needs a password without @@ -49,24 +50,28 @@ class NoPasswordGiven(Exception): """ pass + class InvalidSignature(Exception): """ Raised when signature could not be verified. """ pass + class EncryptionFailed(Exception): """ Raised upon failures of encryption. """ pass + class DecryptionFailed(Exception): """ Raised upon failures of decryption. """ pass + class SignFailed(Exception): """ Raised when failed to sign. diff --git a/src/leap/common/keymanager/openpgp.py b/src/leap/common/keymanager/openpgp.py index 0cb1308..d578638 100644 --- a/src/leap/common/keymanager/openpgp.py +++ b/src/leap/common/keymanager/openpgp.py @@ -66,7 +66,7 @@ def encrypt_sym(data, passphrase, sign=None): leap_assert_type(passphrase, str) if sign is not None: leap_assert_type(sign, OpenPGPKey) - leap_assert(sign.private == True) + leap_assert(sign.private is True) def _encrypt_cb(gpg): result = gpg.encrypt( @@ -105,7 +105,7 @@ def decrypt_sym(data, passphrase, verify=None): leap_assert_type(passphrase, str) if verify is not None: leap_assert_type(verify, OpenPGPKey) - leap_assert(verify.private == False) + leap_assert(verify.private is False) def _decrypt_cb(gpg): result = gpg.decrypt(data, passphrase=passphrase) @@ -145,7 +145,7 @@ def encrypt_asym(data, pubkey, sign=None): leap_assert(pubkey.private is False, 'Key is not public.') if sign is not None: leap_assert_type(sign, OpenPGPKey) - leap_assert(sign.private == True) + leap_assert(sign.private is True) def _encrypt_cb(gpg): result = gpg.encrypt( @@ -185,7 +185,7 @@ def decrypt_asym(data, privkey, verify=None): leap_assert(privkey.private is True, 'Key is not private.') if verify is not None: leap_assert_type(verify, OpenPGPKey) - leap_assert(verify.private == False) + leap_assert(verify.private is False) def _decrypt_cb(gpg): result = gpg.decrypt(data) @@ -258,6 +258,7 @@ def is_encrypted_asym(data): return _safe_call(_is_encrypted_cb) + def sign(data, privkey): """ Sign C{data} with C{privkey}. @@ -271,7 +272,7 @@ def sign(data, privkey): @rtype: str """ leap_assert_type(privkey, OpenPGPKey) - leap_assert(privkey.private == True) + leap_assert(privkey.private is True) def _sign_cb(gpg): result = gpg.sign(data, keyid=privkey.key_id) @@ -289,6 +290,7 @@ def sign(data, privkey): return _safe_call(_sign_cb, [privkey]) + def verify(data, pubkey): """ Verify signed C{data} with C{pubkey}. @@ -302,7 +304,7 @@ def verify(data, pubkey): @rtype: str """ leap_assert_type(pubkey, OpenPGPKey) - leap_assert(pubkey.private == False) + leap_assert(pubkey.private is False) def _verify_cb(gpg): result = gpg.verify(data) @@ -314,6 +316,7 @@ def verify(data, pubkey): return _safe_call(_verify_cb, [pubkey]) + # # Helper functions # @@ -358,8 +361,8 @@ def _build_keyring(keys=[]): @return: A GPG wrapper with a unitary keyring. @rtype: gnupg.GPG """ - privkeys = filter(lambda key: key.private == True, keys) - pubkeys = filter(lambda key: key.private == False, keys) + privkeys = filter(lambda key: key.private is True, keys) + pubkeys = filter(lambda key: key.private is False, keys) # here we filter out public keys that have a correspondent private key in # the list because the private key_data by itself is enough to also have # the public key in the keyring, and we want to count the keys afterwards. diff --git a/src/leap/common/tests/test_keymanager.py b/src/leap/common/tests/test_keymanager.py index 68a30e7..d71167c 100644 --- a/src/leap/common/tests/test_keymanager.py +++ b/src/leap/common/tests/test_keymanager.py @@ -366,9 +366,10 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pubkey2 = pgp.get_key(ADDRESS_2, private=False) privkey2 = pgp.get_key(ADDRESS_2, private=True) data = 'data' - encrypted_and_signed = openpgp.encrypt_asym(data, pubkey2, sign=privkey) + encrypted_and_signed = openpgp.encrypt_asym( + data, pubkey2, sign=privkey) res = openpgp.decrypt_asym( - encrypted_and_signed, privkey2, verify=pubkey) + encrypted_and_signed, privkey2, verify=pubkey) self.assertTrue(data, res) def test_encrypt_sym_sign_decrypt_verify(self): @@ -379,12 +380,12 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pubkey = pgp.get_key(ADDRESS, private=False) encrypted_and_signed = openpgp.encrypt_sym(data, '123', sign=privkey) res = openpgp.decrypt_sym( - encrypted_and_signed, '123', verify=pubkey) + encrypted_and_signed, '123', verify=pubkey) self.assertEqual(data, res) class KeyManagerKeyManagementTestCase( - KeyManagerWithSoledadTestCase): + KeyManagerWithSoledadTestCase): def test_get_all_keys_in_db(self): km = self._key_manager() @@ -473,6 +474,7 @@ class KeyManagerKeyManagementTestCase( class Response(object): status_code = 200 headers = {'content-type': 'application/json'} + def json(self): return {'address': 'anotheruser@leap.se', 'keys': []} @@ -482,7 +484,7 @@ class KeyManagerKeyManagementTestCase( km.fetch_keys_from_server('anotheruser@leap.se') # and verify the call km._fetcher.get.assert_called_once_with( - km._nickserver_url + '/key/' + 'anotheruser@leap.se', + km._nickserver_url + '/key/' + 'anotheruser@leap.se', ) def test_refresh_keys(self): -- cgit v1.2.3 From 5c971f5a57ebac56f27d0374fe24942124be4406 Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 16 May 2013 12:10:23 -0300 Subject: Add crypto submodule that handles AES-256-CTR encryption. --- .../feature_use-pycrypto-for-symmetric-encryption | 1 + pkg/requirements.pip | 3 +- setup.py | 2 + src/leap/common/crypto.py | 114 +++++++++++++++++++++ src/leap/common/tests/test_crypto.py | 80 +++++++++++++++ 5 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 changes/feature_use-pycrypto-for-symmetric-encryption create mode 100644 src/leap/common/crypto.py create mode 100644 src/leap/common/tests/test_crypto.py diff --git a/changes/feature_use-pycrypto-for-symmetric-encryption b/changes/feature_use-pycrypto-for-symmetric-encryption new file mode 100644 index 0000000..5448483 --- /dev/null +++ b/changes/feature_use-pycrypto-for-symmetric-encryption @@ -0,0 +1 @@ + o Add AES-256 (CTR mode) encrypting/decrypting functions using PyCrypto. diff --git a/pkg/requirements.pip b/pkg/requirements.pip index 01fe3ea..141c325 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -5,5 +5,6 @@ pyopenssl python-dateutil autopep8 python-gnupg +PyCrypto -https://protobuf-socket-rpc.googlecode.com/files/protobuf.socketrpc-1.3.2.tar.gz \ No newline at end of file +https://protobuf-socket-rpc.googlecode.com/files/protobuf.socketrpc-1.3.2.tar.gz diff --git a/setup.py b/setup.py index 112736e..99fd25b 100644 --- a/setup.py +++ b/setup.py @@ -28,6 +28,8 @@ requirements = [ "PyOpenSSL", "python-dateutil", "leap.soledad", + "python-gnupg", + "PyCrypto", ] diff --git a/src/leap/common/crypto.py b/src/leap/common/crypto.py new file mode 100644 index 0000000..f49933b --- /dev/null +++ b/src/leap/common/crypto.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +# crypto.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +from Crypto.Cipher import AES +from Crypto.Random import random +from Crypto.Util import Counter +from leap.common.check import leap_assert, leap_assert_type + + +# +# encryption methods +# + +class EncryptionMethods(object): + """ + Representation of encryption methods that can be used. + """ + + AES_256_CTR = 'aes-256-ctr' + + +class UnknownEncryptionMethod(Exception): + """ + Raised when trying to encrypt/decrypt with unknown method. + """ + pass + + +# +# encrypt/decrypt functions +# + +# In the future, we might want to implement other encryption schemes and +# possibly factor out the actual encryption/decryption routines of the +# following functions to specific classes, while not changing the API. + +def encrypt_sym(data, key, method=EncryptionMethods.AES_256_CTR): + """ + Encrypt C{data} with C{key}, using C{method} encryption method. + + @param data: The data to be encrypted. + @type data: str + @param key: The key used to encrypt C{data} (must be 256 bits long). + @type key: str + @param method: The encryption method to use. + @type method: str + + @return: A tuple with the initial value and the encrypted data. + @rtype: (long, str) + """ + leap_assert_type(key, str) + + # AES-256 in CTR mode + if method == EncryptionMethods.AES_256_CTR: + leap_assert( + len(key) == 32, # 32 x 8 = 256 bits. + 'Wrong key size: %s bits (must be 256 bits long).' % (len(key)*8)) + iv = random.getrandbits(256) + ctr = Counter.new(128, initial_value=iv) + cipher = AES.new(key=key, mode=AES.MODE_CTR, counter=ctr) + return iv, cipher.encrypt(data) + + # raise if method is unknown + raise UnknownEncryptionMethod('Unkwnown method: %s' % method) + + +def decrypt_sym(data, key, method=EncryptionMethods.AES_256_CTR, **kwargs): + """ + Decrypt C{data} with C{key} using C{method} encryption method. + + @param data: The data to be decrypted with prepended IV. + @type data: str + @param key: The key used to decrypt C{data} (must be 256 bits long). + @type key: str + @param method: The encryption method to use. + @type method: str + @param kwargs: Other parameters specific to each encryption method. + @type kwargs: long + + @return: The decrypted data. + @rtype: str + """ + leap_assert_type(key, str) + + # AES-256 in CTR mode + if method == EncryptionMethods.AES_256_CTR: + # assert params + leap_assert( + len(key) == 32, # 32 x 8 = 256 bits. + 'Wrong key size: %s (must be 256 bits long).' % len(key)) + leap_assert( + 'iv' in kwargs, + 'AES-256-CTR needs an initial value given as.') + ctr = Counter.new(128, initial_value=kwargs['iv']) + cipher = AES.new(key, AES.MODE_CTR, counter=ctr) + return cipher.decrypt(data) + + # raise if method is unknown + raise UnknownEncryptionMethod('Unkwnown method: %s' % method) diff --git a/src/leap/common/tests/test_crypto.py b/src/leap/common/tests/test_crypto.py new file mode 100644 index 0000000..b704c05 --- /dev/null +++ b/src/leap/common/tests/test_crypto.py @@ -0,0 +1,80 @@ +## -*- coding: utf-8 -*- +# test_crypto.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +""" +Tests for the crypto submodule. +""" + + +from leap.common.testing.basetest import BaseLeapTest +from leap.common import crypto +from Crypto import Random + + +class CryptoTestCase(BaseLeapTest): + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_encrypt_decrypt_sym(self): + # generate 256-bit key + key = Random.new().read(32) + iv, cyphertext = crypto.encrypt_sym( + 'data', key, + method=crypto.EncryptionMethods.AES_256_CTR) + self.assertTrue(cyphertext is not None) + self.assertTrue(cyphertext != '') + self.assertTrue(cyphertext != 'data') + plaintext = crypto.decrypt_sym( + cyphertext, key, iv=iv, + method=crypto.EncryptionMethods.AES_256_CTR) + self.assertEqual('data', plaintext) + + def test_decrypt_with_wrong_iv_fails(self): + key = Random.new().read(32) + iv, cyphertext = crypto.encrypt_sym( + 'data', key, + method=crypto.EncryptionMethods.AES_256_CTR) + self.assertTrue(cyphertext is not None) + self.assertTrue(cyphertext != '') + self.assertTrue(cyphertext != 'data') + iv += 1 + plaintext = crypto.decrypt_sym( + cyphertext, key, iv=iv, + method=crypto.EncryptionMethods.AES_256_CTR) + self.assertNotEqual('data', plaintext) + + def test_decrypt_with_wrong_key_fails(self): + key = Random.new().read(32) + iv, cyphertext = crypto.encrypt_sym( + 'data', key, + method=crypto.EncryptionMethods.AES_256_CTR) + self.assertTrue(cyphertext is not None) + self.assertTrue(cyphertext != '') + self.assertTrue(cyphertext != 'data') + wrongkey = Random.new().read(32) # 256-bits key + # ensure keys are different in case we are extremely lucky + while wrongkey == key: + wrongkey = Random.new().read(32) + plaintext = crypto.decrypt_sym( + cyphertext, wrongkey, iv=iv, + method=crypto.EncryptionMethods.AES_256_CTR) + self.assertNotEqual('data', plaintext) -- cgit v1.2.3 From 9d07bbae0a4d9c64603481a9452b99cbff0f435e Mon Sep 17 00:00:00 2001 From: drebs Date: Fri, 17 May 2013 15:27:06 -0300 Subject: Do not attempt to fetch private keys from server. --- changes/bug_fix-do-not-attempt-to-fetch-privkeys-from-server | 1 + src/leap/common/keymanager/__init__.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 changes/bug_fix-do-not-attempt-to-fetch-privkeys-from-server diff --git a/changes/bug_fix-do-not-attempt-to-fetch-privkeys-from-server b/changes/bug_fix-do-not-attempt-to-fetch-privkeys-from-server new file mode 100644 index 0000000..4c8c0eb --- /dev/null +++ b/changes/bug_fix-do-not-attempt-to-fetch-privkeys-from-server @@ -0,0 +1 @@ + o Fix attempt to fetch private keys from server. diff --git a/src/leap/common/keymanager/__init__.py b/src/leap/common/keymanager/__init__.py index ab28510..3427f03 100644 --- a/src/leap/common/keymanager/__init__.py +++ b/src/leap/common/keymanager/__init__.py @@ -192,7 +192,9 @@ class KeyManager(object): try: return self._wrapper_map[ktype].get_key(address, private=private) except KeyNotFound: - if fetch_remote is False: + # we will only try to fetch a key from nickserver if fetch_remote + # is True and the key is not private. + if fetch_remote is False or private is True: raise # fetch keys from server and discard unwanted types. keys = filter(lambda k: isinstance(k, ktype), -- cgit v1.2.3 From 2a78ab3c730ba652153deaed55cfa8d92089a979 Mon Sep 17 00:00:00 2001 From: drebs Date: Sat, 18 May 2013 12:49:23 -0300 Subject: Adapt keymanager tests for latest Soledad api. --- src/leap/common/tests/test_keymanager.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/leap/common/tests/test_keymanager.py b/src/leap/common/tests/test_keymanager.py index d71167c..48f7273 100644 --- a/src/leap/common/tests/test_keymanager.py +++ b/src/leap/common/tests/test_keymanager.py @@ -143,16 +143,17 @@ class KeyManagerWithSoledadTestCase(BaseLeapTest): def setUp(self): # mock key fetching and storing so Soledad doesn't fail when trying to # reach the server. - Soledad._fetch_keys_from_shared_db = Mock(return_value=None) - Soledad._assert_keys_in_shared_db = Mock(return_value=None) + Soledad._get_secrets_from_shared_db = Mock(return_value=None) + Soledad._put_secrets_in_shared_db = Mock(return_value=None) self._soledad = Soledad( "leap@leap.se", "123456", - secret_path=self.tempdir+"/secret.gpg", - local_db_path=self.tempdir+"/soledad.u1db", - server_url='', - cert_file=None, + self.tempdir+"/secret.gpg", + self.tempdir+"/soledad.u1db", + '', + None, + auth_token=None, ) def tearDown(self): -- cgit v1.2.3 From 7c6a87acaead5f54e1f2155ecf0a089eff97d654 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 13 May 2013 23:06:35 +0900 Subject: use temporary openpgpwrapper as a context manager in this way we implicitely catch any exception during the wrapped call, and ensure that the destructor is always called. --- .gitignore | 2 + changes/feature_openpgp-context-manager | 1 + pkg/requirements-testing.pip | 1 + src/leap/common/keymanager/__init__.py | 3 +- src/leap/common/keymanager/errors.py | 7 + src/leap/common/keymanager/openpgp.py | 526 ++++++++++++++++++------------- src/leap/common/tests/test_keymanager.py | 47 ++- 7 files changed, 344 insertions(+), 243 deletions(-) create mode 100644 changes/feature_openpgp-context-manager create mode 100644 pkg/requirements-testing.pip diff --git a/.gitignore b/.gitignore index 331608c..e0d60cb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ *.pyc *.egg *.egg-info +*.swp +*.swo dist/ build/ diff --git a/changes/feature_openpgp-context-manager b/changes/feature_openpgp-context-manager new file mode 100644 index 0000000..4dbf759 --- /dev/null +++ b/changes/feature_openpgp-context-manager @@ -0,0 +1 @@ + o Refactor opengpg utility functions implementation so it uses a context manager. diff --git a/pkg/requirements-testing.pip b/pkg/requirements-testing.pip new file mode 100644 index 0000000..932a895 --- /dev/null +++ b/pkg/requirements-testing.pip @@ -0,0 +1 @@ +mock diff --git a/src/leap/common/keymanager/__init__.py b/src/leap/common/keymanager/__init__.py index 3427f03..30a9146 100644 --- a/src/leap/common/keymanager/__init__.py +++ b/src/leap/common/keymanager/__init__.py @@ -160,7 +160,8 @@ class KeyManager(object): privkey = self.get_key( self._address, ktype, private=True, fetch_remote=False) privkey = json.loads(privkey.get_json()) - privkey.key_data = encrypt_sym(privkey.key_data, password) + privkey.key_data = encrypt_sym( + privkey.key_data, passphrase=password) data['keys'].append(privkey) self._fetcher.put( self._nickserver_url + '/key/' + self._address, diff --git a/src/leap/common/keymanager/errors.py b/src/leap/common/keymanager/errors.py index d6802f0..89949d2 100644 --- a/src/leap/common/keymanager/errors.py +++ b/src/leap/common/keymanager/errors.py @@ -72,6 +72,13 @@ class DecryptionFailed(Exception): pass +class EncryptionDecryptionFailed(Exception): + """ + Raised upon failures of encryption/decryption. + """ + pass + + class SignFailed(Exception): """ Raised when failed to sign. diff --git a/src/leap/common/keymanager/openpgp.py b/src/leap/common/keymanager/openpgp.py index d578638..e60833b 100644 --- a/src/leap/common/keymanager/openpgp.py +++ b/src/leap/common/keymanager/openpgp.py @@ -15,26 +15,18 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . - """ Infrastructure for using OpenPGP keys in Key Manager. """ - - +import logging +import os import re -import tempfile import shutil +import tempfile from leap.common.check import leap_assert, leap_assert_type -from leap.common.keymanager.errors import ( - KeyNotFound, - KeyAlreadyExists, - KeyAttributesDiffer, - InvalidSignature, - EncryptionFailed, - DecryptionFailed, - SignFailed, -) +from leap.common.keymanager import errors + from leap.common.keymanager.keys import ( EncryptionKey, EncryptionScheme, @@ -44,12 +36,242 @@ from leap.common.keymanager.keys import ( ) from leap.common.keymanager.gpg import GPGWrapper +logger = logging.getLogger(__name__) + + +# +# gpg wrapper and decorator +# + +def temporary_gpgwrapper(keys=None): + """ + Returns a unitary gpg wrapper that implements context manager + protocol. + + @param key_data: ASCII armored key data. + @type key_data: str + + @return: a GPGWrapper instance + @rtype: GPGWrapper + """ + # TODO do here checks on key_data + return TempGPGWrapper(keys=keys) + + +def with_temporary_gpg(fun): + """ + Decorator to add a temporary gpg wrapper as context + to gpg related functions. + + Decorated functions are expected to return a function whose only + argument is a gpgwrapper instance. + """ + def wrapped(*args, **kwargs): + """ + We extract the arguments passed to the wrapped function, + run the function and do validations. + We expect that the positional arguments are `data`, + and an optional `key`. + All the rest of arguments should be passed as named arguments + to allow for a correct unpacking. + """ + if len(args) == 2: + keys = args[1] if isinstance(args[1], OpenPGPKey) else None + else: + keys = None + + # sign/verify keys passed as arguments + sign = kwargs.get('sign', None) + if sign: + keys = [keys, sign] + + verify = kwargs.get('verify', None) + if verify: + keys = [keys, verify] + + # is the wrapped function sign or verify? + fun_name = fun.__name__ + is_sign_function = True if fun_name == "sign" else False + is_verify_function = True if fun_name == "verify" else False + + result = None + + with temporary_gpgwrapper(keys) as gpg: + result = fun(*args, **kwargs)(gpg) + + # TODO: cleanup a little bit the + # validation. maybe delegate to other + # auxiliary functions for clarity. + + ok = getattr(result, 'ok', None) + + stderr = getattr(result, 'stderr', None) + if stderr: + logger.debug("%s" % (stderr,)) + + if ok is False: + raise errors.EncryptionDecryptionFailed( + 'Failed to encrypt/decrypt in %s: %s' % ( + fun.__name__, + stderr)) + + if verify is not None: + # A verify key has been passed + if result.valid is False or \ + verify.fingerprint != result.pubkey_fingerprint: + raise errors.InvalidSignature( + 'Failed to verify signature with key %s: %s' % + (verify.key_id, stderr)) + + if is_sign_function: + # Specific validation for sign function + privkey = gpg.list_keys(secret=True).pop() + rfprint = result.fingerprint + kfprint = privkey['fingerprint'] + if result.fingerprint is None: + raise errors.SignFailed( + 'Failed to sign with key %s: %s' % + (privkey['keyid'], stderr)) + leap_assert( + result.fingerprint == kfprint, + 'Signature and private key fingerprints mismatch: ' + '%s != %s' % + (rfprint, kfprint)) + + if is_verify_function: + # Specific validation for verify function + pubkey = gpg.list_keys().pop() + valid = result.valid + rfprint = result.fingerprint + kfprint = pubkey['fingerprint'] + if valid is False or rfprint != kfprint: + raise errors.InvalidSignature( + 'Failed to verify signature ' + 'with key %s.' % pubkey['keyid']) + result = result.valid + + # ok, enough checks. let's return data if available + if hasattr(result, 'data'): + result = result.data + return result + return wrapped + + +class TempGPGWrapper(object): + """ + A context manager returning a temporary GPG wrapper keyring, which + contains exactly zero or one pubkeys, and zero or one privkeys. + + Temporary unitary keyrings allow the to use GPG's facilities for exactly + one key. This function creates an empty temporary keyring and imports + C{keys} if it is not None. + """ + def __init__(self, keys=None): + """ + @param keys: OpenPGP key, or list of. + @type keys: OpenPGPKey or list of OpenPGPKeys + """ + self._gpg = None + if not keys: + keys = list() + if not isinstance(keys, list): + keys = [keys] + self._keys = keys + for key in filter(None, keys): + leap_assert_type(key, OpenPGPKey) + + def __enter__(self): + """ + Calls the unitary gpgwrapper initializer + + @return: A GPG wrapper with a unitary keyring. + @rtype: gnupg.GPG + """ + self._build_keyring() + return self._gpg + + def __exit__(self, exc_type, exc_value, traceback): + """ + Ensures the gpgwrapper is properly destroyed. + """ + # TODO handle exceptions and log here + self._destroy_keyring() + + def _build_keyring(self): + """ + Create an empty GPG keyring and import C{keys} into it. + + @param keys: List of keys to add to the keyring. + @type keys: list of OpenPGPKey + + @return: A GPG wrapper with a unitary keyring. + @rtype: gnupg.GPG + """ + privkeys = [key for key in self._keys if key and key.private is True] + publkeys = [key for key in self._keys if key and key.private is False] + # here we filter out public keys that have a correspondent + # private key in the list because the private key_data by + # itself is enough to also have the public key in the keyring, + # and we want to count the keys afterwards. + + privaddrs = map(lambda privkey: privkey.address, privkeys) + publkeys = filter( + lambda pubkey: pubkey.address not in privaddrs, publkeys) + + listkeys = lambda: self._gpg.list_keys() + listsecretkeys = lambda: self._gpg.list_keys(secret=True) + + self._gpg = GPGWrapper(gnupghome=tempfile.mkdtemp()) + leap_assert(len(listkeys()) is 0, 'Keyring not empty.') + + # import keys into the keyring: + # concatenating ascii-armored keys, which is correctly + # understood by the GPGWrapper. + + self._gpg.import_keys("".join( + [x.key_data for x in publkeys + privkeys])) + + # assert the number of keys in the keyring + leap_assert( + len(listkeys()) == len(publkeys) + len(privkeys), + 'Wrong number of public keys in keyring: %d, should be %d)' % + (len(listkeys()), len(publkeys) + len(privkeys))) + leap_assert( + len(listsecretkeys()) == len(privkeys), + 'Wrong number of private keys in keyring: %d, should be %d)' % + (len(listsecretkeys()), len(privkeys))) + + def _destroy_keyring(self): + """ + Securely erase a unitary keyring. + """ + # TODO: implement some kind of wiping of data or a more + # secure way that + # does not write to disk. + + try: + for secret in [True, False]: + for key in self._gpg.list_keys(secret=secret): + self._gpg.delete_keys( + key['fingerprint'], + secret=secret) + leap_assert(len(self._gpg.list_keys()) is 0, 'Keyring not empty!') + + except: + raise + + finally: + leap_assert(self._gpg.gnupghome != os.path.expanduser('~/.gnupg'), + "watch out! Tried to remove default gnupg home!") + shutil.rmtree(self._gpg.gnupghome) + # # API functions # -def encrypt_sym(data, passphrase, sign=None): +@with_temporary_gpg +def encrypt_sym(data, passphrase=None, sign=None): """ Encrypt C{data} with C{passphrase} and sign with C{sign} private key. @@ -68,23 +290,19 @@ def encrypt_sym(data, passphrase, sign=None): leap_assert_type(sign, OpenPGPKey) leap_assert(sign.private is True) - def _encrypt_cb(gpg): - result = gpg.encrypt( - data, None, - sign=sign.key_id if sign else None, - passphrase=passphrase, symmetric=True) - # Here we cannot assert for correctness of sig because the sig is in - # the ciphertext. - # result.ok - (bool) indicates if the operation succeeded - # result.data - (bool) contains the result of the operation - if result.ok is False: - raise EncryptionFailed('Failed to encrypt: %s' % result.stderr) - return result.data + # Here we cannot assert for correctness of sig because the sig is in + # the ciphertext. + # result.ok - (bool) indicates if the operation succeeded + # result.data - (bool) contains the result of the operation - return _safe_call(_encrypt_cb, [sign]) + return lambda gpg: gpg.encrypt( + data, None, + sign=sign.key_id if sign else None, + passphrase=passphrase, symmetric=True) -def decrypt_sym(data, passphrase, verify=None): +@with_temporary_gpg +def decrypt_sym(data, passphrase=None, verify=None): """ Decrypt C{data} with C{passphrase} and verify with C{verify} public key. @@ -107,27 +325,17 @@ def decrypt_sym(data, passphrase, verify=None): leap_assert_type(verify, OpenPGPKey) leap_assert(verify.private is False) - def _decrypt_cb(gpg): - result = gpg.decrypt(data, passphrase=passphrase) - # result.ok - (bool) indicates if the operation succeeded - # result.valid - (bool) indicates if the signature was verified - # result.data - (bool) contains the result of the operation - # result.pubkey_fingerpring - (str) contains the fingerprint of the - # public key that signed this data. - if result.ok is False: - raise DecryptionFailed('Failed to decrypt: %s', result.stderr) - if verify is not None: - if result.valid is False or \ - verify.fingerprint != result.pubkey_fingerprint: - raise InvalidSignature( - 'Failed to verify signature with key %s: %s' % - (verify.key_id, result.stderr)) - return result.data - - return _safe_call(_decrypt_cb, [verify]) - - -def encrypt_asym(data, pubkey, sign=None): + # result.ok - (bool) indicates if the operation succeeded + # result.valid - (bool) indicates if the signature was verified + # result.data - (bool) contains the result of the operation + # result.pubkey_fingerpring - (str) contains the fingerprint of the + # public key that signed this data. + return lambda gpg: gpg.decrypt( + data, passphrase=passphrase) + + +@with_temporary_gpg +def encrypt_asym(data, key, passphrase=None, sign=None): """ Encrypt C{data} using public @{key} and sign with C{sign} key. @@ -141,31 +349,25 @@ def encrypt_asym(data, pubkey, sign=None): @return: The encrypted data. @rtype: str """ - leap_assert_type(pubkey, OpenPGPKey) - leap_assert(pubkey.private is False, 'Key is not public.') + leap_assert_type(key, OpenPGPKey) + leap_assert(key.private is False, 'Key is not public.') if sign is not None: leap_assert_type(sign, OpenPGPKey) leap_assert(sign.private is True) - def _encrypt_cb(gpg): - result = gpg.encrypt( - data, pubkey.fingerprint, - sign=sign.key_id if sign else None, - symmetric=False) - # Here we cannot assert for correctness of sig because the sig is in - # the ciphertext. - # result.ok - (bool) indicates if the operation succeeded - # result.data - (bool) contains the result of the operation - if result.ok is False: - raise EncryptionFailed( - 'Failed to encrypt with key %s: %s' % - (pubkey.key_id, result.stderr)) - return result.data - - return _safe_call(_encrypt_cb, [pubkey, sign]) - - -def decrypt_asym(data, privkey, verify=None): + # Here we cannot assert for correctness of sig because the sig is in + # the ciphertext. + # result.ok - (bool) indicates if the operation succeeded + # result.data - (bool) contains the result of the operation + + return lambda gpg: gpg.encrypt( + data, key.fingerprint, + sign=sign.key_id if sign else None, + passphrase=passphrase, symmetric=False) + + +@with_temporary_gpg +def decrypt_asym(data, key, passphrase=None, verify=None): """ Decrypt C{data} using private @{key} and verify with C{verify} key. @@ -182,32 +384,16 @@ def decrypt_asym(data, privkey, verify=None): @raise InvalidSignature: Raised if unable to verify the signature with C{verify} key. """ - leap_assert(privkey.private is True, 'Key is not private.') + leap_assert(key.private is True, 'Key is not private.') if verify is not None: leap_assert_type(verify, OpenPGPKey) leap_assert(verify.private is False) - def _decrypt_cb(gpg): - result = gpg.decrypt(data) - # result.ok - (bool) indicates if the operation succeeded - # result.valid - (bool) indicates if the signature was verified - # result.data - (bool) contains the result of the operation - # result.pubkey_fingerpring - (str) contains the fingerprint of the - # public key that signed this data. - if result.ok is False: - raise DecryptionFailed('Failed to decrypt with key %s: %s' % - (privkey.key_id, result.stderr)) - if verify is not None: - if result.valid is False or \ - verify.fingerprint != result.pubkey_fingerprint: - raise InvalidSignature( - 'Failed to verify signature with key %s: %s' % - (verify.key_id, result.stderr)) - return result.data - - return _safe_call(_decrypt_cb, [privkey, verify]) + return lambda gpg: gpg.decrypt( + data, passphrase=passphrase) +@with_temporary_gpg def is_encrypted(data): """ Return whether C{data} was encrypted using OpenPGP. @@ -218,13 +404,10 @@ def is_encrypted(data): @return: Whether C{data} was encrypted using this wrapper. @rtype: bool """ - - def _is_encrypted_cb(gpg): - return gpg.is_encrypted(data) - - return _safe_call(_is_encrypted_cb) + return lambda gpg: gpg.is_encrypted(data) +@with_temporary_gpg def is_encrypted_sym(data): """ Return whether C{data} was encrypted using a public OpenPGP key. @@ -235,13 +418,10 @@ def is_encrypted_sym(data): @return: Whether C{data} was encrypted using this wrapper. @rtype: bool """ - - def _is_encrypted_cb(gpg): - return gpg.is_encrypted_sym(data) - - return _safe_call(_is_encrypted_cb) + return lambda gpg: gpg.is_encrypted_sym(data) +@with_temporary_gpg def is_encrypted_asym(data): """ Return whether C{data} was asymmetrically encrypted using OpenPGP. @@ -252,19 +432,17 @@ def is_encrypted_asym(data): @return: Whether C{data} was encrypted using this wrapper. @rtype: bool """ - - def _is_encrypted_cb(gpg): - return gpg.is_encrypted_asym(data) - - return _safe_call(_is_encrypted_cb) + return lambda gpg: gpg.is_encrypted_asym(data) +@with_temporary_gpg def sign(data, privkey): """ Sign C{data} with C{privkey}. @param data: The data to be signed. @type data: str + @param privkey: The private key to be used to sign. @type privkey: OpenPGPKey @@ -274,53 +452,36 @@ def sign(data, privkey): leap_assert_type(privkey, OpenPGPKey) leap_assert(privkey.private is True) - def _sign_cb(gpg): - result = gpg.sign(data, keyid=privkey.key_id) - # result.fingerprint - contains the fingerprint of the key used to - # sign. - if result.fingerprint is None: - raise SignFailed( - 'Failed to sign with key %s: %s' % - (privkey.key_id, result.stderr)) - leap_assert( - result.fingerprint == privkey.fingerprint, - 'Signature and private key fingerprints mismatch: %s != %s' % - (result.fingerprint, privkey.fingerprint)) - return result.data - - return _safe_call(_sign_cb, [privkey]) + # result.fingerprint - contains the fingerprint of the key used to + # sign. + return lambda gpg: gpg.sign(data, keyid=privkey.key_id) -def verify(data, pubkey): +@with_temporary_gpg +def verify(data, key): """ Verify signed C{data} with C{pubkey}. @param data: The data to be verified. @type data: str + @param pubkey: The public key to be used on verification. @type pubkey: OpenPGPKey @return: The ascii-armored signed data. @rtype: str """ - leap_assert_type(pubkey, OpenPGPKey) - leap_assert(pubkey.private is False) - - def _verify_cb(gpg): - result = gpg.verify(data) - if result.valid is False or \ - result.fingerprint != pubkey.fingerprint: - raise InvalidSignature( - 'Failed to verify signature with key %s.' % pubkey.key_id) - return True + leap_assert_type(key, OpenPGPKey) + leap_assert(key.private is False) - return _safe_call(_verify_cb, [pubkey]) + return lambda gpg: gpg.verify(data) # # Helper functions # + def _build_key_from_gpg(address, key, key_data): """ Build an OpenPGPKey for C{address} based on C{key} from @@ -350,81 +511,6 @@ def _build_key_from_gpg(address, key, key_data): ) -def _build_keyring(keys=[]): - """ - - Create an empty GPG keyring and import C{keys} into it. - - @param keys: List of keys to add to the keyring. - @type keys: list of OpenPGPKey - - @return: A GPG wrapper with a unitary keyring. - @rtype: gnupg.GPG - """ - privkeys = filter(lambda key: key.private is True, keys) - pubkeys = filter(lambda key: key.private is False, keys) - # here we filter out public keys that have a correspondent private key in - # the list because the private key_data by itself is enough to also have - # the public key in the keyring, and we want to count the keys afterwards. - privaddrs = map(lambda privkey: privkey.address, privkeys) - pubkeys = filter(lambda pubkey: pubkey.address not in privaddrs, pubkeys) - # create temporary dir for temporary gpg keyring - tmpdir = tempfile.mkdtemp() - gpg = GPGWrapper(gnupghome=tmpdir) - leap_assert(len(gpg.list_keys()) is 0, 'Keyring not empty.') - # import keys into the keyring - gpg.import_keys( - reduce( - lambda x, y: x+y, - [key.key_data for key in pubkeys+privkeys], '')) - # assert the number of keys in the keyring - leap_assert( - len(gpg.list_keys()) == len(pubkeys)+len(privkeys), - 'Wrong number of public keys in keyring: %d, should be %d)' % - (len(gpg.list_keys()), len(pubkeys)+len(privkeys))) - leap_assert( - len(gpg.list_keys(secret=True)) == len(privkeys), - 'Wrong number of private keys in keyring: %d, should be %d)' % - (len(gpg.list_keys(secret=True)), len(privkeys))) - return gpg - - -def _destroy_keyring(gpg): - """ - Securely erase a keyring. - - @param gpg: A GPG wrapper instance. - @type gpg: gnupg.GPG - """ - for secret in [True, False]: - for key in gpg.list_keys(secret=secret): - gpg.delete_keys( - key['fingerprint'], - secret=secret) - leap_assert(len(gpg.list_keys()) is 0, 'Keyring not empty!') - # TODO: implement some kind of wiping of data or a more secure way that - # does not write to disk. - shutil.rmtree(gpg.gnupghome) - - -def _safe_call(callback, keys=[]): - """ - Run C{callback} over a keyring containing C{keys}. - - @param callback: Function whose first argument is the gpg keyring. - @type callback: function(gnupg.GPG) - @param keys: List of keys to add to the keyring. - @type keys: list of OpenPGPKey - - @return: The results of the callback. - @rtype: str or bool - """ - gpg = _build_keyring(filter(lambda key: key is not None, keys)) - val = callback(gpg) - _destroy_keyring(gpg) - return val - - # # The OpenPGP wrapper # @@ -463,11 +549,11 @@ class OpenPGPScheme(EncryptionScheme): leap_assert(is_address(address), 'Not an user address: %s' % address) try: self.get_key(address) - raise KeyAlreadyExists(address) - except KeyNotFound: + raise errors.KeyAlreadyExists(address) + except errors.KeyNotFound: pass - def _gen_key_cb(gpg): + def _gen_key(gpg): params = gpg.gen_key_input( key_type='RSA', key_length=4096, @@ -495,7 +581,10 @@ class OpenPGPScheme(EncryptionScheme): gpg.export_keys(key['fingerprint'], secret=secret)) self.put_key(openpgp_key) - _safe_call(_gen_key_cb) + with temporary_gpgwrapper() as gpg: + # TODO: inspect result, or use decorator + _gen_key(gpg) + return self.get_key(address, private=True) def get_key(self, address, private=False): @@ -514,7 +603,7 @@ class OpenPGPScheme(EncryptionScheme): leap_assert(is_address(address), 'Not an user address: %s' % address) doc = self._get_key_doc(address, private) if doc is None: - raise KeyNotFound(address) + raise errors.KeyNotFound(address) return build_key_from_dict(OpenPGPKey, address, doc.content) def put_ascii_key(self, key_data): @@ -525,11 +614,14 @@ class OpenPGPScheme(EncryptionScheme): @type key_data: str """ leap_assert_type(key_data, str) + # TODO: add more checks for correct key data. + leap_assert(key_data is not None, 'Data does not represent a key.') - def _put_ascii_key_cb(gpg): + def _put_ascii_key(gpg): gpg.import_keys(key_data) privkey = None pubkey = None + try: privkey = gpg.list_keys(secret=True).pop() except IndexError: @@ -561,7 +653,9 @@ class OpenPGPScheme(EncryptionScheme): gpg.export_keys(pubkey['fingerprint'], secret=False)) self.put_key(openpgp_pubkey) - _safe_call(_put_ascii_key_cb) + with temporary_gpgwrapper() as gpg: + # TODO: inspect result, or use decorator + _put_ascii_key(gpg) def put_key(self, key): """ @@ -606,9 +700,9 @@ class OpenPGPScheme(EncryptionScheme): leap_assert(key.__class__ is OpenPGPKey, 'Wrong key type.') stored_key = self.get_key(key.address, private=key.private) if stored_key is None: - raise KeyNotFound(key) + raise errors.KeyNotFound(key) if stored_key.__dict__ != key.__dict__: - raise KeyAttributesDiffer(key) + raise errors.KeyAttributesDiffer(key) doc = self._soledad.get_doc( keymanager_doc_id(OpenPGPKey, key.address, key.private)) self._soledad.delete_doc(doc) diff --git a/src/leap/common/tests/test_keymanager.py b/src/leap/common/tests/test_keymanager.py index 48f7273..a7aa1ca 100644 --- a/src/leap/common/tests/test_keymanager.py +++ b/src/leap/common/tests/test_keymanager.py @@ -30,16 +30,15 @@ except ImportError: from leap.common.testing.basetest import BaseLeapTest from leap.soledad import Soledad -from leap.soledad.crypto import SoledadCrypto - +#from leap.soledad.crypto import SoledadCrypto from leap.common.keymanager import ( KeyManager, openpgp, KeyNotFound, NoPasswordGiven, - TAGS_INDEX, - TAGS_AND_PRIVATE_INDEX, + #TAGS_INDEX, + #TAGS_AND_PRIVATE_INDEX, ) from leap.common.keymanager.openpgp import OpenPGPKey from leap.common.keymanager.keys import ( @@ -240,14 +239,16 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): KeyNotFound, pgp.get_key, ADDRESS, private=True) def test_openpgp_encrypt_decrypt_sym(self): - cyphertext = openpgp.encrypt_sym('data', 'pass') + cyphertext = openpgp.encrypt_sym( + 'data', passphrase='pass') self.assertTrue(cyphertext is not None) self.assertTrue(cyphertext != '') self.assertTrue(cyphertext != 'data') self.assertTrue(openpgp.is_encrypted_sym(cyphertext)) self.assertFalse(openpgp.is_encrypted_asym(cyphertext)) self.assertTrue(openpgp.is_encrypted(cyphertext)) - plaintext = openpgp.decrypt_sym(cyphertext, 'pass') + plaintext = openpgp.decrypt_sym( + cyphertext, passphrase='pass') self.assertEqual('data', plaintext) def test_verify_with_private_raises(self): @@ -298,7 +299,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pubkey = pgp.get_key(ADDRESS, private=False) self.assertRaises( AssertionError, - openpgp.encrypt_sym, data, '123', sign=pubkey) + openpgp.encrypt_sym, data, passphrase='123', sign=pubkey) def test_decrypt_asym_verify_with_private_raises(self): pgp = openpgp.OpenPGPScheme(self._soledad) @@ -321,22 +322,11 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pubkey = pgp.get_key(ADDRESS, private=False) encrypted_and_signed = openpgp.encrypt_asym(data, pubkey, sign=privkey) pgp.put_ascii_key(PUBLIC_KEY_2) - wrongkey = pgp.get_key('anotheruser@leap.se') + wrongkey = pgp.get_key(ADDRESS_2) self.assertRaises( errors.InvalidSignature, openpgp.verify, encrypted_and_signed, wrongkey) - def test_decrypt_sym_verify_with_private_raises(self): - pgp = openpgp.OpenPGPScheme(self._soledad) - pgp.put_ascii_key(PRIVATE_KEY) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - encrypted_and_signed = openpgp.encrypt_sym(data, '123', sign=privkey) - self.assertRaises( - AssertionError, - openpgp.decrypt_sym, - encrypted_and_signed, 'decrypt', verify=privkey) - def test_decrypt_sym_verify_with_private_raises(self): pgp = openpgp.OpenPGPScheme(self._soledad) pgp.put_ascii_key(PRIVATE_KEY) @@ -344,7 +334,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): privkey = pgp.get_key(ADDRESS, private=True) encrypted_and_signed = openpgp.encrypt_sym(data, '123', sign=privkey) pgp.put_ascii_key(PUBLIC_KEY_2) - wrongkey = pgp.get_key('anotheruser@leap.se') + wrongkey = pgp.get_key(ADDRESS_2) self.assertRaises( errors.InvalidSignature, openpgp.verify, encrypted_and_signed, wrongkey) @@ -385,8 +375,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): self.assertEqual(data, res) -class KeyManagerKeyManagementTestCase( - KeyManagerWithSoledadTestCase): +class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): def test_get_all_keys_in_db(self): km = self._key_manager() @@ -477,15 +466,15 @@ class KeyManagerKeyManagementTestCase( headers = {'content-type': 'application/json'} def json(self): - return {'address': 'anotheruser@leap.se', 'keys': []} + return {'address': ADDRESS_2, 'keys': []} km._fetcher.get = Mock( return_value=Response()) # do the fetch - km.fetch_keys_from_server('anotheruser@leap.se') + km.fetch_keys_from_server(ADDRESS_2) # and verify the call km._fetcher.get.assert_called_once_with( - km._nickserver_url + '/key/' + 'anotheruser@leap.se', + km._nickserver_url + '/key/' + ADDRESS_2, ) def test_refresh_keys(self): @@ -495,11 +484,13 @@ class KeyManagerKeyManagementTestCase( km.fetch_keys_from_server = Mock(return_value=[]) km.refresh_keys() km.fetch_keys_from_server.assert_called_once_with( - 'leap@leap.se' + ADDRESS ) # Key material for testing + +# key 24D18DDF: public key "Leap Test Key " KEY_FINGERPRINT = "E36E738D69173C13D709E44F2F455E2824D18DDF" PUBLIC_KEY = """ -----BEGIN PGP PUBLIC KEY BLOCK----- @@ -662,6 +653,7 @@ RZXoH+FTg9UAW87eqU610npOkT6cRaBxaMK/mDtGNdc= -----END PGP PRIVATE KEY BLOCK----- """ +# key 7FEE575A: public key "anotheruser " PUBLIC_KEY_2 = """ -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1.4.10 (GNU/Linux) @@ -719,3 +711,6 @@ THx7N776fcYHGumbqUMYrxrcZSbNveE6SaK8fphRam1dewM0 =a5gs -----END PGP PRIVATE KEY BLOCK----- """ +import unittest +if __name__ == "__main__": + unittest.main() -- cgit v1.2.3 From c2b8ebb38a72acd2f60659241883a146cc384aec Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 20 May 2013 17:53:45 -0300 Subject: Adapt get_key() and send_key() to the spec. * Use session_id instead of token for now. * Receive info needed to interact with webapp API as params and setters/getters. * Use the webapp API for sending the key to server. * Prevent from refreshing own key. --- src/leap/common/keymanager/__init__.py | 246 +++++++++++++++++++++---------- src/leap/common/keymanager/keys.py | 72 ++++++--- src/leap/common/tests/test_keymanager.py | 113 +++++++++----- 3 files changed, 288 insertions(+), 143 deletions(-) diff --git a/src/leap/common/keymanager/__init__.py b/src/leap/common/keymanager/__init__.py index 30a9146..ad9bb3b 100644 --- a/src/leap/common/keymanager/__init__.py +++ b/src/leap/common/keymanager/__init__.py @@ -34,6 +34,7 @@ from leap.common.keymanager.errors import ( ) from leap.common.keymanager.keys import ( build_key_from_dict, + KEYMANAGER_KEY_TAG, ) from leap.common.keymanager.openpgp import ( OpenPGPKey, @@ -42,6 +43,10 @@ from leap.common.keymanager.openpgp import ( ) +# +# key indexing constants. +# + TAGS_INDEX = 'by-tags' TAGS_AND_PRIVATE_INDEX = 'by-tags-and-private' INDEXES = { @@ -50,9 +55,21 @@ INDEXES = { } +# +# The Key Manager +# + class KeyManager(object): - def __init__(self, address, nickserver_url, soledad, token=None): + # + # server's key storage constants + # + + OPENPGP_KEY = 'openpgp' + PUBKEY_KEY = "user[public_key]" + + def __init__(self, address, nickserver_uri, soledad, session_id=None, + ca_cert_path=None, api_uri=None, api_version=None, uid=None): """ Initialize a Key Manager for user's C{address} with provider's nickserver reachable in C{url}. @@ -63,17 +80,35 @@ class KeyManager(object): @type url: str @param soledad: A Soledad instance for local storage of keys. @type soledad: leap.soledad.Soledad + @param session_id: The session ID for interacting with the webapp API. + @type session_id: str + @param ca_cert_path: The path to the CA certificate. + @type ca_cert_path: str + @param api_uri: The URI of the webapp API. + @type api_uri: str + @param api_version: The version of the webapp API. + @type api_version: str + @param uid: The users' UID. + @type uid: str """ self._address = address - self._nickserver_url = nickserver_url + self._nickserver_uri = nickserver_uri self._soledad = soledad - self.token = token + self._session_id = session_id + self.ca_cert_path = ca_cert_path + self.api_uri = api_uri + self.api_version = api_version + self.uid = uid + # a dict to map key types to their handlers self._wrapper_map = { OpenPGPKey: OpenPGPScheme(soledad), # other types of key will be added to this mapper. } + # initialize the indexes needed to query the database self._init_indexes() + # the following are used to perform https requests self._fetcher = requests + self._session = self._fetcher.session() # # utilities @@ -107,24 +142,81 @@ class KeyManager(object): self._soledad.delete_index(name) self._soledad.create_index(name, *expression) - def _get_dict_from_http_json(self, path): + def _get(self, uri, data=None): """ - Make a GET HTTP request and return a dictionary containing the - response. + Send a GET request to C{uri} containing C{data}. + + @param uri: The URI of the request. + @type uri: str + @param data: The body of the request. + @type data: dict, str or file + + @return: The response to the request. + @rtype: requests.Response """ - response = self._fetcher.get(self._nickserver_url+path) - leap_assert(response.status_code == 200, 'Invalid response.') leap_assert( - response.headers['content-type'].startswith('application/json') - is True, + self._ca_cert_path is not None, + 'We need the CA certificate path!') + res = self._fetcher.get(uri, data=data, verify=self._ca_cert_path) + # assert that the response is valid + res.raise_for_status() + leap_assert( + res.headers['content-type'].startswith('application/json'), 'Content-type is not JSON.') - return response.json() + return res + + def _put(self, uri, data=None): + """ + Send a PUT request to C{uri} containing C{data}. + + The request will be sent using the configured CA certificate path to + verify the server certificate and the configured session id for + authentication. + + @param uri: The URI of the request. + @type uri: str + @param data: The body of the request. + @type data: dict, str or file + + @return: The response to the request. + @rtype: requests.Response + """ + leap_assert( + self._ca_cert_path is not None, + 'We need the CA certificate path!') + leap_assert( + self._session_id is not None, + 'We need a session_id to interact with webapp!') + res = self._fetcher.put( + uri, data=data, verify=self._ca_cert_path, + cookies={'_session_id': self._session_id}) + # assert that the response is valid + res.raise_for_status() + return res + + def _fetch_keys_from_server(self, address): + """ + Fetch keys bound to C{address} from nickserver and insert them in + local database. + + @param address: The address bound to the keys. + @type address: str + + @raise KeyNotFound: If the key was not found on nickserver. + """ + # request keys from the nickserver + server_keys = self._get( + self._nickserver_uri, {'address': address}).json() + # insert keys in local database + if self.OPENPGP_KEY in server_keys: + self._wrapper_map[OpenPGPKey].put_ascii_key( + server_keys['openpgp']) # # key management # - def send_key(self, ktype, send_private=False, password=None): + def send_key(self, ktype): """ Send user's key of type C{ktype} to provider. @@ -140,33 +232,22 @@ class KeyManager(object): @param ktype: The type of the key. @type ktype: KeyType - @raise httplib.HTTPException: - @raise KeyNotFound: If the key was not found both locally and in - keyserver. + @raise KeyNotFound: If the key was not found in local database. """ + leap_assert( + ktype is OpenPGPKey, + 'For now we only know how to send OpenPGP public keys.') # prepare the public key bound to address pubkey = self.get_key( self._address, ktype, private=False, fetch_remote=False) data = { - 'address': self._address, - 'keys': [ - json.loads(pubkey.get_json()), - ] + self.PUBKEY_KEY: pubkey.key_data } - # prepare the private key bound to address - if send_private: - if password is None or password == '': - raise NoPasswordGiven('Can\'t send unencrypted private keys!') - privkey = self.get_key( - self._address, ktype, private=True, fetch_remote=False) - privkey = json.loads(privkey.get_json()) - privkey.key_data = encrypt_sym( - privkey.key_data, passphrase=password) - data['keys'].append(privkey) - self._fetcher.put( - self._nickserver_url + '/key/' + self._address, - data=data, - auth=(self._address, self._token)) + uri = "%s/%s/users/%s.json" % ( + self._api_uri, + self._api_version, + self._uid) + self._put(uri, data) def get_key(self, address, ktype, private=False, fetch_remote=True): """ @@ -191,48 +272,15 @@ class KeyManager(object): ktype in self._wrapper_map, 'Unkown key type: %s.' % str(ktype)) try: + # return key if it exists in local database return self._wrapper_map[ktype].get_key(address, private=private) except KeyNotFound: # we will only try to fetch a key from nickserver if fetch_remote # is True and the key is not private. if fetch_remote is False or private is True: raise - # fetch keys from server and discard unwanted types. - keys = filter(lambda k: isinstance(k, ktype), - self.fetch_keys_from_server(address)) - if len(keys) is 0: - raise KeyNotFound() - leap_assert( - len(keys) == 1, - 'Got more than one key of type %s for %s.' % - (str(ktype), address)) - self._wrapper_map[ktype].put_key(keys[0]) - return self._wrapper_map[ktype].get_key(address, private=private) - - def fetch_keys_from_server(self, address): - """ - Fetch keys bound to C{address} from nickserver. - - @param address: The address bound to the keys. - @type address: str - - @return: A list of keys bound to C{address}. - @rtype: list of EncryptionKey - @raise KeyNotFound: If the key was not found on nickserver. - @raise httplib.HTTPException: - """ - keydata = self._get_dict_from_http_json('/key/%s' % address) - leap_assert( - keydata['address'] == address, - "Fetched key for wrong address.") - keys = [] - for key in keydata['keys']: - keys.append( - build_key_from_dict( - self._key_class_from_type(key['type']), - address, - key)) - return keys + self._fetch_keys_from_server(address) + return self._wrapper_map[ktype].get_key(address, private=False) def get_all_keys_in_local_db(self, private=False): """ @@ -248,7 +296,7 @@ class KeyManager(object): doc.content), self._soledad.get_from_index( TAGS_AND_PRIVATE_INDEX, - 'keymanager-key', + KEYMANAGER_KEY_TAG, '1' if private else '0')) def refresh_keys(self): @@ -258,10 +306,11 @@ class KeyManager(object): addresses = set(map( lambda doc: doc.address, self.get_all_keys_in_local_db(private=False))) - # TODO: maybe we should not attempt to refresh our own public key? for address in addresses: - for key in self.fetch_keys_from_server(address): - self._wrapper_map[key.__class__].put_key(key) + # do not attempt to refresh our own key + if address == self._address: + continue + self._fetch_keys_from_server(address) def gen_key(self, ktype): """ @@ -276,14 +325,51 @@ class KeyManager(object): return self._wrapper_map[ktype].gen_key(self._address) # - # Token setter/getter + # Setters/getters # - def _get_token(self): - return self._token + def _get_session_id(self): + return self._session_id + + def _set_session_id(self, session_id): + self._session_id = session_id + + session_id = property( + _get_session_id, _set_session_id, doc='The session id.') + + def _get_ca_cert_path(self): + return self._ca_cert_path + + def _set_ca_cert_path(self, ca_cert_path): + self._ca_cert_path = ca_cert_path + + ca_cert_path = property( + _get_ca_cert_path, _set_ca_cert_path, + doc='The path to the CA certificate.') + + def _get_api_uri(self): + return self._api_uri + + def _set_api_uri(self, api_uri): + self._api_uri = api_uri + + api_uri = property( + _get_api_uri, _set_api_uri, doc='The webapp API URI.') + + def _get_api_version(self): + return self._api_version + + def _set_api_version(self, api_version): + self._api_version = api_version + + api_version = property( + _get_api_version, _set_api_version, doc='The webapp API version.') + + def _get_uid(self): + return self._uid - def _set_token(self, token): - self._token = token + def _set_uid(self, uid): + self._uid = uid - token = property( - _get_token, _set_token, doc='The auth token.') + uid = property( + _get_uid, _set_uid, doc='The uid of the user.') diff --git a/src/leap/common/keymanager/keys.py b/src/leap/common/keymanager/keys.py index d98bd02..1d87858 100644 --- a/src/leap/common/keymanager/keys.py +++ b/src/leap/common/keymanager/keys.py @@ -33,6 +33,30 @@ from abc import ABCMeta, abstractmethod from leap.common.check import leap_assert +# +# Dictionary keys used for storing cryptographic keys. +# + +KEY_ADDRESS_KEY = 'address' +KEY_TYPE_KEY = 'type' +KEY_ID_KEY = 'key_id' +KEY_FINGERPRINT_KEY = 'fingerprint' +KEY_DATA_KEY = 'key_data' +KEY_PRIVATE_KEY = 'private' +KEY_LENGTH_KEY = 'length' +KEY_EXPIRY_DATE_KEY = 'expiry_date' +KEY_FIRST_SEEN_AT_KEY = 'first_seen_at' +KEY_LAST_AUDITED_AT_KEY = 'last_audited_at' +KEY_VALIDATION_KEY = 'validation' +KEY_TAGS_KEY = 'tags' + + +# +# Key storage constants +# + +KEYMANAGER_KEY_TAG = 'keymanager-key' + # # Key handling utilities # @@ -60,18 +84,20 @@ def build_key_from_dict(kClass, address, kdict): @return: An instance of the key. @rtype: C{kClass} """ - leap_assert(address == kdict['address'], 'Wrong address in key data.') + leap_assert( + address == kdict[KEY_ADDRESS_KEY], + 'Wrong address in key data.') return kClass( address, - key_id=kdict['key_id'], - fingerprint=kdict['fingerprint'], - key_data=kdict['key_data'], - private=kdict['private'], - length=kdict['length'], - expiry_date=kdict['expiry_date'], - first_seen_at=kdict['first_seen_at'], - last_audited_at=kdict['last_audited_at'], - validation=kdict['validation'], # TODO: verify for validation. + key_id=kdict[KEY_ID_KEY], + fingerprint=kdict[KEY_FINGERPRINT_KEY], + key_data=kdict[KEY_DATA_KEY], + private=kdict[KEY_PRIVATE_KEY], + length=kdict[KEY_LENGTH_KEY], + expiry_date=kdict[KEY_EXPIRY_DATE_KEY], + first_seen_at=kdict[KEY_FIRST_SEEN_AT_KEY], + last_audited_at=kdict[KEY_LAST_AUDITED_AT_KEY], + validation=kdict[KEY_VALIDATION_KEY], # TODO: verify for validation. ) @@ -92,7 +118,7 @@ def keymanager_doc_id(ktype, address, private=False): """ leap_assert(is_address(address), "Wrong address format: %s" % address) ktype = str(ktype) - visibility = 'private' if private else 'public' + visibility = KEY_PRIVATE_KEY if private else 'public' return sha256('keymanager-'+address+'-'+ktype+'-'+visibility).hexdigest() @@ -141,18 +167,18 @@ class EncryptionKey(object): @rtype: str """ return json.dumps({ - 'address': self.address, - 'type': str(self.__class__), - 'key_id': self.key_id, - 'fingerprint': self.fingerprint, - 'key_data': self.key_data, - 'private': self.private, - 'length': self.length, - 'expiry_date': self.expiry_date, - 'validation': self.validation, - 'first_seen_at': self.first_seen_at, - 'last_audited_at': self.last_audited_at, - 'tags': ['keymanager-key'], + KEY_ADDRESS_KEY: self.address, + KEY_TYPE_KEY: str(self.__class__), + KEY_ID_KEY: self.key_id, + KEY_FINGERPRINT_KEY: self.fingerprint, + KEY_DATA_KEY: self.key_data, + KEY_PRIVATE_KEY: self.private, + KEY_LENGTH_KEY: self.length, + KEY_EXPIRY_DATE_KEY: self.expiry_date, + KEY_VALIDATION_KEY: self.validation, + KEY_FIRST_SEEN_AT_KEY: self.first_seen_at, + KEY_LAST_AUDITED_AT_KEY: self.last_audited_at, + KEY_TAGS_KEY: [KEYMANAGER_KEY_TAG], }) def __repr__(self): diff --git a/src/leap/common/tests/test_keymanager.py b/src/leap/common/tests/test_keymanager.py index a7aa1ca..dcd525c 100644 --- a/src/leap/common/tests/test_keymanager.py +++ b/src/leap/common/tests/test_keymanager.py @@ -419,73 +419,106 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km = self._key_manager() self.assertRaises( KeyNotFound, - km.send_key, OpenPGPKey, send_private=False) + km.send_key, OpenPGPKey) - def test_send_private_key_raises_key_not_found(self): - km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY) - self.assertRaises( - KeyNotFound, - km.send_key, OpenPGPKey, send_private=True, - password='123') - - def test_send_private_key_without_password_raises(self): - km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY) - self.assertRaises( - NoPasswordGiven, - km.send_key, OpenPGPKey, send_private=True) - - def test_send_public_key(self): + def test_send_key(self): + """ + Test that request is well formed when sending keys to server. + """ km = self._key_manager() km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY) km._fetcher.put = Mock() - km.token = '123' - km.send_key(OpenPGPKey, send_private=False) - # setup args + # the following data will be used on the send + km.ca_cert_path = 'capath' + km.session_id = 'sessionid' + km.uid = 'myuid' + km.api_uri = 'apiuri' + km.api_version = 'apiver' + km.send_key(OpenPGPKey) + # setup expected args data = { - 'address': km._address, - 'keys': [ - json.loads( - km.get_key( - km._address, OpenPGPKey).get_json()), - ] + km.PUBKEY_KEY: km.get_key(km._address, OpenPGPKey).key_data, } - url = km._nickserver_url + '/key/' + km._address - + url = '%s/%s/users/%s.json' % ('apiuri', 'apiver', 'myuid') km._fetcher.put.assert_called_once_with( - url, data=data, auth=(km._address, '123') + url, data=data, verify='capath', + cookies={'_session_id': 'sessionid'}, ) - def test_fetch_keys_from_server(self): - km = self._key_manager() - # setup mock + def test__fetch_keys_from_server(self): + """ + Test that the request is well formed when fetching keys from server. + """ + km = self._key_manager(url='http://nickserver.domain') class Response(object): status_code = 200 headers = {'content-type': 'application/json'} def json(self): - return {'address': ADDRESS_2, 'keys': []} + return {'address': ADDRESS_2, 'openpgp': PUBLIC_KEY_2} + + def raise_for_status(self): + pass + # mock the fetcher so it returns the key for ADDRESS_2 km._fetcher.get = Mock( return_value=Response()) + km.ca_cert_path = 'cacertpath' # do the fetch - km.fetch_keys_from_server(ADDRESS_2) + km._fetch_keys_from_server(ADDRESS_2) # and verify the call km._fetcher.get.assert_called_once_with( - km._nickserver_url + '/key/' + ADDRESS_2, + 'http://nickserver.domain', + data={'address': ADDRESS_2}, + verify='cacertpath', ) - def test_refresh_keys(self): - # TODO: maybe we should not attempt to refresh our own public key? + def test_refresh_keys_does_not_refresh_own_key(self): + """ + Test that refreshing keys will not attempt to refresh our own key. + """ km = self._key_manager() + # we add 2 keys but we expect it to only refresh the second one. km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY) - km.fetch_keys_from_server = Mock(return_value=[]) + km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY_2) + # mock the key fetching + km._fetch_keys_from_server = Mock(return_value=[]) + km.ca_cert_path = '' # some bogus path so the km does not complain. + # do the refreshing km.refresh_keys() - km.fetch_keys_from_server.assert_called_once_with( - ADDRESS + km._fetch_keys_from_server.assert_called_once_with( + ADDRESS_2 + ) + + def test_get_key_fetches_from_server(self): + """ + Test that getting a key successfuly fetches from server. + """ + km = self._key_manager(url='http://nickserver.domain') + + class Response(object): + status_code = 200 + headers = {'content-type': 'application/json'} + + def json(self): + return {'address': ADDRESS_2, 'openpgp': PUBLIC_KEY_2} + + def raise_for_status(self): + pass + + # mock the fetcher so it returns the key for ADDRESS_2 + km._fetcher.get = Mock(return_value=Response()) + km.ca_cert_path = 'cacertpath' + # try to key get without fetching from server + self.assertRaises( + KeyNotFound, km.get_key, ADDRESS_2, OpenPGPKey, + fetch_remote=False ) + # try to get key fetching from server. + key = km.get_key(ADDRESS_2, OpenPGPKey) + self.assertIsInstance(key, OpenPGPKey) + self.assertEqual(ADDRESS_2, key.address) # Key material for testing -- cgit v1.2.3 From b16437ac68a72b128e3771e0847f376237f649a3 Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 21 May 2013 17:22:14 -0300 Subject: Remove openpgp symmetric encryption. --- src/leap/common/crypto.py | 4 +- src/leap/common/keymanager/__init__.py | 1 - src/leap/common/keymanager/openpgp.py | 78 -------------------------------- src/leap/common/tests/test_keymanager.py | 46 ------------------- 4 files changed, 2 insertions(+), 127 deletions(-) diff --git a/src/leap/common/crypto.py b/src/leap/common/crypto.py index f49933b..d7a8457 100644 --- a/src/leap/common/crypto.py +++ b/src/leap/common/crypto.py @@ -83,14 +83,14 @@ def decrypt_sym(data, key, method=EncryptionMethods.AES_256_CTR, **kwargs): """ Decrypt C{data} with C{key} using C{method} encryption method. - @param data: The data to be decrypted with prepended IV. + @param data: The data to be decrypted. @type data: str @param key: The key used to decrypt C{data} (must be 256 bits long). @type key: str @param method: The encryption method to use. @type method: str @param kwargs: Other parameters specific to each encryption method. - @type kwargs: long + @type kwargs: dict @return: The decrypted data. @rtype: str diff --git a/src/leap/common/keymanager/__init__.py b/src/leap/common/keymanager/__init__.py index ad9bb3b..7aaeddf 100644 --- a/src/leap/common/keymanager/__init__.py +++ b/src/leap/common/keymanager/__init__.py @@ -39,7 +39,6 @@ from leap.common.keymanager.keys import ( from leap.common.keymanager.openpgp import ( OpenPGPKey, OpenPGPScheme, - encrypt_sym, ) diff --git a/src/leap/common/keymanager/openpgp.py b/src/leap/common/keymanager/openpgp.py index e60833b..d53afd6 100644 --- a/src/leap/common/keymanager/openpgp.py +++ b/src/leap/common/keymanager/openpgp.py @@ -270,70 +270,6 @@ class TempGPGWrapper(object): # API functions # -@with_temporary_gpg -def encrypt_sym(data, passphrase=None, sign=None): - """ - Encrypt C{data} with C{passphrase} and sign with C{sign} private key. - - @param data: The data to be encrypted. - @type data: str - @param passphrase: The passphrase used to encrypt C{data}. - @type passphrase: str - @param sign: The private key used for signing. - @type sign: OpenPGPKey - - @return: The encrypted data. - @rtype: str - """ - leap_assert_type(passphrase, str) - if sign is not None: - leap_assert_type(sign, OpenPGPKey) - leap_assert(sign.private is True) - - # Here we cannot assert for correctness of sig because the sig is in - # the ciphertext. - # result.ok - (bool) indicates if the operation succeeded - # result.data - (bool) contains the result of the operation - - return lambda gpg: gpg.encrypt( - data, None, - sign=sign.key_id if sign else None, - passphrase=passphrase, symmetric=True) - - -@with_temporary_gpg -def decrypt_sym(data, passphrase=None, verify=None): - """ - Decrypt C{data} with C{passphrase} and verify with C{verify} public - key. - - @param data: The data to be decrypted. - @type data: str - @param passphrase: The passphrase used to decrypt C{data}. - @type passphrase: str - @param verify: The key used to verify a signature. - @type verify: OpenPGPKey - - @return: The decrypted data. - @rtype: str - - @raise InvalidSignature: Raised if unable to verify the signature with - C{verify} key. - """ - leap_assert_type(passphrase, str) - if verify is not None: - leap_assert_type(verify, OpenPGPKey) - leap_assert(verify.private is False) - - # result.ok - (bool) indicates if the operation succeeded - # result.valid - (bool) indicates if the signature was verified - # result.data - (bool) contains the result of the operation - # result.pubkey_fingerpring - (str) contains the fingerprint of the - # public key that signed this data. - return lambda gpg: gpg.decrypt( - data, passphrase=passphrase) - - @with_temporary_gpg def encrypt_asym(data, key, passphrase=None, sign=None): """ @@ -407,20 +343,6 @@ def is_encrypted(data): return lambda gpg: gpg.is_encrypted(data) -@with_temporary_gpg -def is_encrypted_sym(data): - """ - Return whether C{data} was encrypted using a public OpenPGP key. - - @param data: The data we want to know about. - @type data: str - - @return: Whether C{data} was encrypted using this wrapper. - @rtype: bool - """ - return lambda gpg: gpg.is_encrypted_sym(data) - - @with_temporary_gpg def is_encrypted_asym(data): """ diff --git a/src/leap/common/tests/test_keymanager.py b/src/leap/common/tests/test_keymanager.py index dcd525c..cffa073 100644 --- a/src/leap/common/tests/test_keymanager.py +++ b/src/leap/common/tests/test_keymanager.py @@ -223,7 +223,6 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): self.assertTrue(cyphertext != '') self.assertTrue(cyphertext != 'data') self.assertTrue(openpgp.is_encrypted_asym(cyphertext)) - self.assertFalse(openpgp.is_encrypted_sym(cyphertext)) self.assertTrue(openpgp.is_encrypted(cyphertext)) # decrypt self.assertRaises( @@ -238,19 +237,6 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): self.assertRaises( KeyNotFound, pgp.get_key, ADDRESS, private=True) - def test_openpgp_encrypt_decrypt_sym(self): - cyphertext = openpgp.encrypt_sym( - 'data', passphrase='pass') - self.assertTrue(cyphertext is not None) - self.assertTrue(cyphertext != '') - self.assertTrue(cyphertext != 'data') - self.assertTrue(openpgp.is_encrypted_sym(cyphertext)) - self.assertFalse(openpgp.is_encrypted_asym(cyphertext)) - self.assertTrue(openpgp.is_encrypted(cyphertext)) - plaintext = openpgp.decrypt_sym( - cyphertext, passphrase='pass') - self.assertEqual('data', plaintext) - def test_verify_with_private_raises(self): pgp = openpgp.OpenPGPScheme(self._soledad) pgp.put_ascii_key(PRIVATE_KEY) @@ -292,15 +278,6 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): AssertionError, openpgp.encrypt_asym, data, privkey, sign=pubkey) - def test_encrypt_sym_sign_with_public_raises(self): - pgp = openpgp.OpenPGPScheme(self._soledad) - pgp.put_ascii_key(PUBLIC_KEY) - data = 'data' - pubkey = pgp.get_key(ADDRESS, private=False) - self.assertRaises( - AssertionError, - openpgp.encrypt_sym, data, passphrase='123', sign=pubkey) - def test_decrypt_asym_verify_with_private_raises(self): pgp = openpgp.OpenPGPScheme(self._soledad) pgp.put_ascii_key(PRIVATE_KEY) @@ -327,18 +304,6 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): errors.InvalidSignature, openpgp.verify, encrypted_and_signed, wrongkey) - def test_decrypt_sym_verify_with_private_raises(self): - pgp = openpgp.OpenPGPScheme(self._soledad) - pgp.put_ascii_key(PRIVATE_KEY) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - encrypted_and_signed = openpgp.encrypt_sym(data, '123', sign=privkey) - pgp.put_ascii_key(PUBLIC_KEY_2) - wrongkey = pgp.get_key(ADDRESS_2) - self.assertRaises( - errors.InvalidSignature, - openpgp.verify, encrypted_and_signed, wrongkey) - def test_sign_verify(self): pgp = openpgp.OpenPGPScheme(self._soledad) pgp.put_ascii_key(PRIVATE_KEY) @@ -363,17 +328,6 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): encrypted_and_signed, privkey2, verify=pubkey) self.assertTrue(data, res) - def test_encrypt_sym_sign_decrypt_verify(self): - pgp = openpgp.OpenPGPScheme(self._soledad) - pgp.put_ascii_key(PRIVATE_KEY) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - pubkey = pgp.get_key(ADDRESS, private=False) - encrypted_and_signed = openpgp.encrypt_sym(data, '123', sign=privkey) - res = openpgp.decrypt_sym( - encrypted_and_signed, '123', verify=pubkey) - self.assertEqual(data, res) - class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): -- cgit v1.2.3 From b45d207241dc22eda1129bc37d7a0948235b20fb Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 23 May 2013 05:23:37 +0900 Subject: make more compact debug info by truncating strings --- src/leap/common/events/component.py | 6 +++--- src/leap/common/events/server.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/leap/common/events/component.py b/src/leap/common/events/component.py index 98a97bc..f62f948 100644 --- a/src/leap/common/events/component.py +++ b/src/leap/common/events/component.py @@ -137,7 +137,7 @@ def register(signal, callback, uid=None, replace=False, reqcbk=None, logger.info( "Sending registration request to server on port %s: %s", server.SERVER_PORT, - str(request)) + str(request)[:40]) return service.register(request, callback=reqcbk, timeout=timeout) @@ -178,7 +178,7 @@ def signal(signal, content="", mac_method="", mac="", reqcbk=None, request.mac = mac service = RpcService(proto.EventsServerService_Stub, server.SERVER_PORT, 'localhost') - logger.info("Sending signal to server: %s", str(request)) + logger.info("Sending signal to server: %s", str(request)[:40]) return service.signal(request, callback=reqcbk, timeout=timeout) @@ -204,7 +204,7 @@ class EventsComponentService(proto.EventsComponentService): @param done: callback to be called when done @type done: protobuf.socketrpc.server.Callback """ - logger.info('Received signal from server: %s' % str(request)) + logger.info('Received signal from server: %s...' % str(request)[:40]) # run registered callbacks # TODO: verify authentication using mac in incoming message diff --git a/src/leap/common/events/server.py b/src/leap/common/events/server.py index 16c6513..34c573a 100644 --- a/src/leap/common/events/server.py +++ b/src/leap/common/events/server.py @@ -88,7 +88,7 @@ class EventsServerService(proto.EventsServerService): @param done: callback to be called when done @type done: protobuf.socketrpc.server.Callback """ - logger.info("Received registration request: %s" % str(request)) + logger.info("Received registration request: %s..." % str(request)[:40]) # add component port to signal list if request.event not in registered_components: registered_components[request.event] = set([]) @@ -112,7 +112,7 @@ class EventsServerService(proto.EventsServerService): @param done: callback to be called when done @type done: protobuf.socketrpc.server.Callback """ - logger.info('Received signal from component: %s', str(request)) + logger.info('Received signal from component: %s...', str(request)[:40]) # send signal to all registered components # TODO: verify signal auth if request.event in registered_components: -- cgit v1.2.3 From b7dc270bce885ce5079d86d8e52fd96b8390fc9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 23 May 2013 11:09:31 -0300 Subject: Properly use AES in CTR mode --- src/leap/common/crypto.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/leap/common/crypto.py b/src/leap/common/crypto.py index d7a8457..3d6030b 100644 --- a/src/leap/common/crypto.py +++ b/src/leap/common/crypto.py @@ -15,13 +15,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os +import binascii from Crypto.Cipher import AES -from Crypto.Random import random from Crypto.Util import Counter from leap.common.check import leap_assert, leap_assert_type - # # encryption methods # @@ -70,10 +70,10 @@ def encrypt_sym(data, key, method=EncryptionMethods.AES_256_CTR): leap_assert( len(key) == 32, # 32 x 8 = 256 bits. 'Wrong key size: %s bits (must be 256 bits long).' % (len(key)*8)) - iv = random.getrandbits(256) - ctr = Counter.new(128, initial_value=iv) + iv = os.urandom(8) + ctr = Counter.new(64, prefix=iv) cipher = AES.new(key=key, mode=AES.MODE_CTR, counter=ctr) - return iv, cipher.encrypt(data) + return binascii.b2a_base64(iv), cipher.encrypt(data) # raise if method is unknown raise UnknownEncryptionMethod('Unkwnown method: %s' % method) @@ -106,8 +106,8 @@ def decrypt_sym(data, key, method=EncryptionMethods.AES_256_CTR, **kwargs): leap_assert( 'iv' in kwargs, 'AES-256-CTR needs an initial value given as.') - ctr = Counter.new(128, initial_value=kwargs['iv']) - cipher = AES.new(key, AES.MODE_CTR, counter=ctr) + ctr = Counter.new(64, prefix=binascii.a2b_base64(kwargs['iv'])) + cipher = AES.new(key=key, mode=AES.MODE_CTR, counter=ctr) return cipher.decrypt(data) # raise if method is unknown -- cgit v1.2.3 From 2a81dd8b4c885870933f76dec23807f1d5a1a91c Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 28 May 2013 11:00:13 -0300 Subject: Use indexes to fetch keys. --- src/leap/common/keymanager/__init__.py | 37 +------------------ src/leap/common/keymanager/keys.py | 63 +++++++++++++++++++++----------- src/leap/common/keymanager/openpgp.py | 26 ++++++++----- src/leap/common/tests/test_keymanager.py | 17 --------- 4 files changed, 59 insertions(+), 84 deletions(-) diff --git a/src/leap/common/keymanager/__init__.py b/src/leap/common/keymanager/__init__.py index 7aaeddf..b6bef21 100644 --- a/src/leap/common/keymanager/__init__.py +++ b/src/leap/common/keymanager/__init__.py @@ -35,6 +35,7 @@ from leap.common.keymanager.errors import ( from leap.common.keymanager.keys import ( build_key_from_dict, KEYMANAGER_KEY_TAG, + TAGS_PRIVATE_INDEX, ) from leap.common.keymanager.openpgp import ( OpenPGPKey, @@ -42,18 +43,6 @@ from leap.common.keymanager.openpgp import ( ) -# -# key indexing constants. -# - -TAGS_INDEX = 'by-tags' -TAGS_AND_PRIVATE_INDEX = 'by-tags-and-private' -INDEXES = { - TAGS_INDEX: ['tags'], - TAGS_AND_PRIVATE_INDEX: ['tags', 'bool(private)'], -} - - # # The Key Manager # @@ -103,8 +92,6 @@ class KeyManager(object): OpenPGPKey: OpenPGPScheme(soledad), # other types of key will be added to this mapper. } - # initialize the indexes needed to query the database - self._init_indexes() # the following are used to perform https requests self._fetcher = requests self._session = self._fetcher.session() @@ -121,26 +108,6 @@ class KeyManager(object): lambda klass: str(klass) == ktype, self._wrapper_map).pop() - def _init_indexes(self): - """ - Initialize the database indexes. - """ - # Ask the database for currently existing indexes. - db_indexes = dict(self._soledad.list_indexes()) - # Loop through the indexes we expect to find. - for name, expression in INDEXES.items(): - if name not in db_indexes: - # The index does not yet exist. - self._soledad.create_index(name, *expression) - continue - if expression == db_indexes[name]: - # The index exists and is up to date. - continue - # The index exists but the definition is not what expected, so we - # delete it and add the proper index expression. - self._soledad.delete_index(name) - self._soledad.create_index(name, *expression) - def _get(self, uri, data=None): """ Send a GET request to C{uri} containing C{data}. @@ -294,7 +261,7 @@ class KeyManager(object): doc.content['address'], doc.content), self._soledad.get_from_index( - TAGS_AND_PRIVATE_INDEX, + TAGS_PRIVATE_INDEX, KEYMANAGER_KEY_TAG, '1' if private else '0')) diff --git a/src/leap/common/keymanager/keys.py b/src/leap/common/keymanager/keys.py index 1d87858..be1f113 100644 --- a/src/leap/common/keymanager/keys.py +++ b/src/leap/common/keymanager/keys.py @@ -28,7 +28,6 @@ except ImportError: import re -from hashlib import sha256 from abc import ABCMeta, abstractmethod from leap.common.check import leap_assert @@ -57,6 +56,26 @@ KEY_TAGS_KEY = 'tags' KEYMANAGER_KEY_TAG = 'keymanager-key' + +# +# key indexing constants. +# + +TAGS_PRIVATE_INDEX = 'by-tags-private' +TAGS_ADDRESS_PRIVATE_INDEX = 'by-tags-address-private' +INDEXES = { + TAGS_PRIVATE_INDEX: [ + KEY_TAGS_KEY, + 'bool(%s)' % KEY_PRIVATE_KEY, + ], + TAGS_ADDRESS_PRIVATE_INDEX: [ + KEY_TAGS_KEY, + KEY_ADDRESS_KEY, + 'bool(%s)' % KEY_PRIVATE_KEY, + ] +} + + # # Key handling utilities # @@ -101,27 +120,6 @@ def build_key_from_dict(kClass, address, kdict): ) -def keymanager_doc_id(ktype, address, private=False): - """ - Return the document id for the document containing a key for - C{address}. - - @param address: The type of the key. - @type address: KeyType - @param address: The address bound to the key. - @type address: str - @param private: Whether the key is private or not. - @type private: bool - @return: The document id for the document that stores a key bound to - C{address}. - @rtype: str - """ - leap_assert(is_address(address), "Wrong address format: %s" % address) - ktype = str(ktype) - visibility = KEY_PRIVATE_KEY if private else 'public' - return sha256('keymanager-'+address+'-'+ktype+'-'+visibility).hexdigest() - - # # Abstraction for encryption keys # @@ -215,6 +213,27 @@ class EncryptionScheme(object): @type soledad: leap.soledad.Soledad """ self._soledad = soledad + self._init_indexes() + + def _init_indexes(self): + """ + Initialize the database indexes. + """ + # Ask the database for currently existing indexes. + db_indexes = dict(self._soledad.list_indexes()) + # Loop through the indexes we expect to find. + for name, expression in INDEXES.items(): + if name not in db_indexes: + # The index does not yet exist. + self._soledad.create_index(name, *expression) + continue + if expression == db_indexes[name]: + # The index exists and is up to date. + continue + # The index exists but the definition is not what expected, so we + # delete it and add the proper index expression. + self._soledad.delete_index(name) + self._soledad.create_index(name, *expression) @abstractmethod def get_key(self, address, private=False): diff --git a/src/leap/common/keymanager/openpgp.py b/src/leap/common/keymanager/openpgp.py index d53afd6..db9efeb 100644 --- a/src/leap/common/keymanager/openpgp.py +++ b/src/leap/common/keymanager/openpgp.py @@ -26,13 +26,13 @@ import tempfile from leap.common.check import leap_assert, leap_assert_type from leap.common.keymanager import errors - from leap.common.keymanager.keys import ( EncryptionKey, EncryptionScheme, is_address, - keymanager_doc_id, build_key_from_dict, + KEYMANAGER_KEY_TAG, + TAGS_ADDRESS_PRIVATE_INDEX, ) from leap.common.keymanager.gpg import GPGWrapper @@ -588,10 +588,7 @@ class OpenPGPScheme(EncryptionScheme): """ doc = self._get_key_doc(key.address, private=key.private) if doc is None: - self._soledad.create_doc_from_json( - key.get_json(), - doc_id=keymanager_doc_id( - OpenPGPKey, key.address, key.private)) + self._soledad.create_doc_from_json(key.get_json()) else: doc.set_json(key.get_json()) self._soledad.put_doc(doc) @@ -609,8 +606,18 @@ class OpenPGPScheme(EncryptionScheme): @return: The document with the key or None if it does not exist. @rtype: leap.soledad.backends.leap_backend.LeapDocument """ - return self._soledad.get_doc( - keymanager_doc_id(OpenPGPKey, address, private)) + doclist = self._soledad.get_from_index( + TAGS_ADDRESS_PRIVATE_INDEX, + KEYMANAGER_KEY_TAG, + address, + '1' if private else '0') + if len(doclist) is 0: + return None + leap_assert( + len(doclist) is 1, + 'Found more than one %s key for address!' % + 'private' if private else 'public') + return doclist.pop() def delete_key(self, key): """ @@ -625,6 +632,5 @@ class OpenPGPScheme(EncryptionScheme): raise errors.KeyNotFound(key) if stored_key.__dict__ != key.__dict__: raise errors.KeyAttributesDiffer(key) - doc = self._soledad.get_doc( - keymanager_doc_id(OpenPGPKey, key.address, key.private)) + doc = self._get_key_doc(key.address, key.private) self._soledad.delete_doc(doc) diff --git a/src/leap/common/tests/test_keymanager.py b/src/leap/common/tests/test_keymanager.py index cffa073..73611b6 100644 --- a/src/leap/common/tests/test_keymanager.py +++ b/src/leap/common/tests/test_keymanager.py @@ -44,7 +44,6 @@ from leap.common.keymanager.openpgp import OpenPGPKey from leap.common.keymanager.keys import ( is_address, build_key_from_dict, - keymanager_doc_id, ) from leap.common.keymanager import errors @@ -120,22 +119,6 @@ class KeyManagerUtilTestCase(BaseLeapTest): kdict['validation'], key.validation, 'Wrong data in key.') - def test_keymanager_doc_id(self): - doc_id1 = keymanager_doc_id( - OpenPGPKey, ADDRESS, private=False) - doc_id2 = keymanager_doc_id( - OpenPGPKey, ADDRESS, private=True) - doc_id3 = keymanager_doc_id( - OpenPGPKey, 'user@leap.se', private=False) - doc_id4 = keymanager_doc_id( - OpenPGPKey, 'user@leap.se', private=True) - self.assertFalse(doc_id1 == doc_id2, 'Doc ids are equal!') - self.assertFalse(doc_id1 == doc_id3, 'Doc ids are equal!') - self.assertFalse(doc_id1 == doc_id4, 'Doc ids are equal!') - self.assertFalse(doc_id2 == doc_id3, 'Doc ids are equal!') - self.assertFalse(doc_id2 == doc_id4, 'Doc ids are equal!') - self.assertFalse(doc_id3 == doc_id4, 'Doc ids are equal!') - class KeyManagerWithSoledadTestCase(BaseLeapTest): -- cgit v1.2.3 From 7fc904d797cb3c07f593157df1126b4179fe48d8 Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 28 May 2013 11:41:33 -0300 Subject: Fix wrong iv test to account for new form of iv. --- src/leap/common/tests/test_crypto.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/leap/common/tests/test_crypto.py b/src/leap/common/tests/test_crypto.py index b704c05..ae7dc71 100644 --- a/src/leap/common/tests/test_crypto.py +++ b/src/leap/common/tests/test_crypto.py @@ -21,6 +21,10 @@ Tests for the crypto submodule. """ +import os +import binascii + + from leap.common.testing.basetest import BaseLeapTest from leap.common import crypto from Crypto import Random @@ -56,9 +60,13 @@ class CryptoTestCase(BaseLeapTest): self.assertTrue(cyphertext is not None) self.assertTrue(cyphertext != '') self.assertTrue(cyphertext != 'data') - iv += 1 + # get a different iv by changing the first byte + rawiv = binascii.a2b_base64(iv) + wrongiv = rawiv + while wrongiv == rawiv: + wrongiv = os.urandom(1) + rawiv[1:] plaintext = crypto.decrypt_sym( - cyphertext, key, iv=iv, + cyphertext, key, iv=binascii.b2a_base64(wrongiv), method=crypto.EncryptionMethods.AES_256_CTR) self.assertNotEqual('data', plaintext) -- cgit v1.2.3 From 9c12f4cce20ea1dea7e39cda583cb29ded4b4e1a Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 29 May 2013 04:04:14 +0900 Subject: allow absolute paths to config.load --- changes/bug_allow-absolute-paths | 1 + src/leap/common/config/baseconfig.py | 69 ++++++++++++++++++++---------------- 2 files changed, 40 insertions(+), 30 deletions(-) create mode 100644 changes/bug_allow-absolute-paths diff --git a/changes/bug_allow-absolute-paths b/changes/bug_allow-absolute-paths new file mode 100644 index 0000000..deaff1f --- /dev/null +++ b/changes/bug_allow-absolute-paths @@ -0,0 +1 @@ + o Allow absolute paths in baseconfig.load diff --git a/src/leap/common/config/baseconfig.py b/src/leap/common/config/baseconfig.py index 146f1e4..2beb4ce 100644 --- a/src/leap/common/config/baseconfig.py +++ b/src/leap/common/config/baseconfig.py @@ -36,18 +36,19 @@ logger = logging.getLogger(__name__) class BaseConfig: """ - Abstract base class for any JSON based configuration + Abstract base class for any JSON based configuration. """ __metaclass__ = ABCMeta """ - Standalone is a class wide parameter + Standalone is a class wide parameter. - @param standalone: if True it will return the prefix for a - standalone application. Otherwise, it will return the system - default for configuration storage. - @type standalone: bool + :param standalone: if True it will return the prefix for a + standalone application. Otherwise, it will + return the system + default for configuration storage. + :type standalone: bool """ standalone = False @@ -58,13 +59,13 @@ class BaseConfig: @abstractmethod def _get_spec(self): """ - Returns the spec object for the specific configuration + Returns the spec object for the specific configuration. """ return None def _safe_get_value(self, key): """ - Tries to return a value only if the config has already been loaded + Tries to return a value only if the config has already been loaded. @rtype: depends on the config structure, dict, str, array, int @return: returns the value for the specified key in the config @@ -88,14 +89,14 @@ class BaseConfig: def save(self, path_list): """ - Saves the current configuration to disk + Saves the current configuration to disk. - @param path_list: list of components that form the relative - path to configuration. The absolute path will be calculated - depending on the platform. - @type path_list: list + :param path_list: list of components that form the relative + path to configuration. The absolute path + will be calculated depending on the platform. + :type path_list: list - @return: True if saved to disk correctly, False otherwise + :return: True if saved to disk correctly, False otherwise """ config_path = os.path.join(self.get_path_prefix(), *(path_list[:-1])) mkdir_p(config_path) @@ -108,19 +109,27 @@ class BaseConfig: raise return True - def load(self, path="", data=None, mtime=None): + def load(self, path="", data=None, mtime=None, relative=True): """ - Loads the configuration from disk + Loads the configuration from disk. - @type path: str - @param path: relative path to configuration. The absolute path - will be calculated depending on the platform + :param path: if relative=True, this is a relative path + to configuration. The absolute path + will be calculated depending on the platform + :type path: str - @return: True if loaded from disk correctly, False otherwise + :param relative: if True, path is relative. If False, it's absolute. + :type relative: bool + + :return: True if loaded from disk correctly, False otherwise + :rtype: bool """ - config_path = os.path.join(self.get_path_prefix(), - path) + if relative is True: + config_path = os.path.join( + self.get_path_prefix(), path) + else: + config_path = path self._config_checker = PluggableConfig(format="json") self._config_checker.options = copy.deepcopy(self._get_spec()) @@ -131,8 +140,8 @@ class BaseConfig: else: self._config_checker.load(data, mtime=mtime) except Exception as e: - logger.warning("Something went wrong while loading " + - "the config from %s\n%s" % (config_path, e)) + logger.error("Something went wrong while loading " + + "the config from %s\n%s" % (config_path, e)) self._config_checker = None return False return True @@ -140,7 +149,7 @@ class BaseConfig: class LocalizedKey(object): """ - Decorator used for keys that are localized in a configuration + Decorator used for keys that are localized in a configuration. """ def __init__(self, func, **kwargs): @@ -149,13 +158,13 @@ class LocalizedKey(object): def __call__(self, instance, lang="en"): """ Tries to return the string for the specified language, otherwise - informs the problem and returns an empty string + informs the problem and returns an empty string. - @param lang: language code - @type lang: str + :param lang: language code + :type lang: str - @return: localized value from the possible values returned by - self._func + :return: localized value from the possible values returned by + self._func """ descriptions = self._func(instance) description_lang = "" -- cgit v1.2.3 From 354467c605f07042568c10c3e931f7fc093c0bf4 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 29 May 2013 05:00:22 +0900 Subject: change docstring comments to use sphinx style --- src/leap/common/certs.py | 48 +++---- src/leap/common/check.py | 16 +-- src/leap/common/config/baseconfig.py | 4 +- src/leap/common/config/prefixers.py | 16 +-- src/leap/common/crypto.py | 40 +++--- src/leap/common/events/__init__.py | 56 ++++---- src/leap/common/events/component.py | 80 +++++------ src/leap/common/events/daemon.py | 40 +++--- src/leap/common/events/server.py | 40 +++--- src/leap/common/files.py | 26 ++-- src/leap/common/keymanager/__init__.py | 92 ++++++------- src/leap/common/keymanager/gpg.py | 222 +++++++++++++++---------------- src/leap/common/keymanager/keys.py | 72 +++++----- src/leap/common/keymanager/openpgp.py | 160 +++++++++++----------- src/leap/common/testing/basetest.py | 16 +-- src/leap/common/testing/test_basetest.py | 4 +- 16 files changed, 466 insertions(+), 466 deletions(-) diff --git a/src/leap/common/certs.py b/src/leap/common/certs.py index 4cb70dd..4fe563b 100644 --- a/src/leap/common/certs.py +++ b/src/leap/common/certs.py @@ -35,10 +35,10 @@ def get_cert_from_string(string): """ Returns the x509 from the contents of this string - @param string: certificate contents as downloaded - @type string: str + :param string: certificate contents as downloaded + :type string: str - @return: x509 or None + :return: x509 or None """ leap_assert(string, "We need something to load") @@ -55,10 +55,10 @@ def get_privatekey_from_string(string): """ Returns the private key from the contents of this string - @param string: private key contents as downloaded - @type string: str + :param string: private key contents as downloaded + :type string: str - @return: private key or None + :return: private key or None """ leap_assert(string, "We need something to load") @@ -75,12 +75,12 @@ def get_digest(cert_data, method): """ Returns the digest for the cert_data using the method specified - @param cert_data: certificate data in string form - @type cert_data: str - @param method: method to be used for digest - @type method: str + :param cert_data: certificate data in string form + :type cert_data: str + :param method: method to be used for digest + :type method: str - @rtype: str + :rtype: str """ x509 = get_cert_from_string(cert_data) digest = x509.digest(method).replace(":", "").lower() @@ -93,10 +93,10 @@ def can_load_cert_and_pkey(string): Loads certificate and private key from a buffer, returns True if everything went well, False otherwise - @param string: buffer containing the cert and private key - @type string: str or any kind of buffer + :param string: buffer containing the cert and private key + :type string: str or any kind of buffer - @rtype: bool + :rtype: bool """ can_load = True @@ -118,10 +118,10 @@ def is_valid_pemfile(cert): """ Checks that the passed string is a valid pem certificate - @param cert: String containing pem content - @type cert: str + :param cert: String containing pem content + :type cert: str - @rtype: bool + :rtype: bool """ leap_assert(cert, "We need a cert to load") @@ -132,10 +132,10 @@ def get_cert_time_boundaries(certfile): """ Returns the time boundaries for the certificate saved in certfile - @param certfile: path to certificate - @type certfile: str + :param certfile: path to certificate + :type certfile: str - @rtype: tuple (from, to) + :rtype: tuple (from, to) """ cert = get_cert_from_string(certfile) leap_assert(cert, 'There was a problem loading the certificate') @@ -151,11 +151,11 @@ def should_redownload(certfile, now=time.gmtime): """ Returns True if any of the checks don't pass, False otherwise - @param certfile: path to certificate - @type certfile: str - @param now: current date function, ONLY USED FOR TESTING + :param certfile: path to certificate + :type certfile: str + :param now: current date function, ONLY USED FOR TESTING - @rtype: bool + :rtype: bool """ exists = os.path.isfile(certfile) diff --git a/src/leap/common/check.py b/src/leap/common/check.py index 359673b..a2d39a6 100644 --- a/src/leap/common/check.py +++ b/src/leap/common/check.py @@ -31,10 +31,10 @@ def leap_assert(condition, message=""): Asserts the condition and displays the message if that's not met. It also logs the error and its backtrace. - @param condition: condition to check - @type condition: bool - @param message: message to display if the condition isn't met - @type message: str + :param condition: condition to check + :type condition: bool + :param message: message to display if the condition isn't met + :type message: str """ if not condition: logger.error("Bug: %s" % (message,)) @@ -51,10 +51,10 @@ def leap_assert_type(var, expectedType): """ Helper assert check for a variable's expected type - @param var: variable to check - @type var: any - @param expectedType: type to check agains - @type expectedType: type + :param var: variable to check + :type var: any + :param expectedType: type to check agains + :type expectedType: type """ leap_assert(isinstance(var, expectedType), "Expected type %r instead of %r" % diff --git a/src/leap/common/config/baseconfig.py b/src/leap/common/config/baseconfig.py index 2beb4ce..e6bd9c4 100644 --- a/src/leap/common/config/baseconfig.py +++ b/src/leap/common/config/baseconfig.py @@ -67,8 +67,8 @@ class BaseConfig: """ Tries to return a value only if the config has already been loaded. - @rtype: depends on the config structure, dict, str, array, int - @return: returns the value for the specified key in the config + :rtype: depends on the config structure, dict, str, array, int + :return: returns the value for the specified key in the config """ leap_assert(self._config_checker, "Load the config first") return self._config_checker.config.get(key, None) diff --git a/src/leap/common/config/prefixers.py b/src/leap/common/config/prefixers.py index 27274bd..050d4cd 100644 --- a/src/leap/common/config/prefixers.py +++ b/src/leap/common/config/prefixers.py @@ -39,10 +39,10 @@ class Prefixer: """ Returns the platform dependant path prefixer - @param standalone: if True it will return the prefix for a + :param standalone: if True it will return the prefix for a standalone application. Otherwise, it will return the system default for configuration storage. - @type standalone: bool + :type standalone: bool """ return "" @@ -65,10 +65,10 @@ class LinuxPrefixer(Prefixer): This method expects an env variable named LEAP_CLIENT_PATH if standalone is used. - @param standalone: if True it will return the prefix for a + :param standalone: if True it will return the prefix for a standalone application. Otherwise, it will return the system default for configuration storage. - @type standalone: bool + :type standalone: bool """ config_dir = BaseDirectory.xdg_config_home if not standalone: @@ -87,10 +87,10 @@ class DarwinPrefixer(Prefixer): This method expects an env variable named LEAP_CLIENT_PATH if standalone is used. - @param standalone: if True it will return the prefix for a + :param standalone: if True it will return the prefix for a standalone application. Otherwise, it will return the system default for configuration storage. - @type standalone: bool + :type standalone: bool """ config_dir = BaseDirectory.xdg_config_home if not standalone: @@ -109,10 +109,10 @@ class WindowsPrefixer(Prefixer): This method expects an env variable named LEAP_CLIENT_PATH if standalone is used. - @param standalone: if True it will return the prefix for a + :param standalone: if True it will return the prefix for a standalone application. Otherwise, it will return the system default for configuration storage. - @type standalone: bool + :type standalone: bool """ config_dir = BaseDirectory.xdg_config_home diff --git a/src/leap/common/crypto.py b/src/leap/common/crypto.py index d7a8457..8a2ff20 100644 --- a/src/leap/common/crypto.py +++ b/src/leap/common/crypto.py @@ -53,15 +53,15 @@ def encrypt_sym(data, key, method=EncryptionMethods.AES_256_CTR): """ Encrypt C{data} with C{key}, using C{method} encryption method. - @param data: The data to be encrypted. - @type data: str - @param key: The key used to encrypt C{data} (must be 256 bits long). - @type key: str - @param method: The encryption method to use. - @type method: str - - @return: A tuple with the initial value and the encrypted data. - @rtype: (long, str) + :param data: The data to be encrypted. + :type data: str + :param key: The key used to encrypt C{data} (must be 256 bits long). + :type key: str + :param method: The encryption method to use. + :type method: str + + :return: A tuple with the initial value and the encrypted data. + :rtype: (long, str) """ leap_assert_type(key, str) @@ -83,17 +83,17 @@ def decrypt_sym(data, key, method=EncryptionMethods.AES_256_CTR, **kwargs): """ Decrypt C{data} with C{key} using C{method} encryption method. - @param data: The data to be decrypted. - @type data: str - @param key: The key used to decrypt C{data} (must be 256 bits long). - @type key: str - @param method: The encryption method to use. - @type method: str - @param kwargs: Other parameters specific to each encryption method. - @type kwargs: dict - - @return: The decrypted data. - @rtype: str + :param data: The data to be decrypted. + :type data: str + :param key: The key used to decrypt C{data} (must be 256 bits long). + :type key: str + :param method: The encryption method to use. + :type method: str + :param kwargs: Other parameters specific to each encryption method. + :type kwargs: dict + + :return: The decrypted data. + :rtype: str """ leap_assert_type(key, str) diff --git a/src/leap/common/events/__init__.py b/src/leap/common/events/__init__.py index c949080..9fc93ee 100644 --- a/src/leap/common/events/__init__.py +++ b/src/leap/common/events/__init__.py @@ -45,24 +45,24 @@ def register(signal, callback, uid=None, replace=False, reqcbk=None, returned for a synch request but nothing will be returned for an asynch request. - @param signal: the signal that causes the callback to be launched - @type signal: int (see the `events.proto` file) - @param callback: the callback to be called when the signal is received - @type callback: function - @param uid: a unique id for the callback - @type uid: int - @param replace: should an existent callback with same uid be replaced? - @type replace: bool - @param reqcbk: a callback to be called when a response from server is + :param signal: the signal that causes the callback to be launched + :type signal: int (see the `events.proto` file) + :param callback: the callback to be called when the signal is received + :type callback: function + :param uid: a unique id for the callback + :type uid: int + :param replace: should an existent callback with same uid be replaced? + :type replace: bool + :param reqcbk: a callback to be called when a response from server is received - @type reqcbk: function + :type reqcbk: function callback(leap.common.events.events_pb2.EventResponse) - @param timeout: the timeout for synch calls - @type timeout: int + :param timeout: the timeout for synch calls + :type timeout: int - @return: the response from server for synch calls or nothing for asynch + :return: the response from server for synch calls or nothing for asynch calls - @rtype: leap.common.events.events_pb2.EventsResponse or None + :rtype: leap.common.events.events_pb2.EventsResponse or None """ return component.register(signal, callback, uid, replace, reqcbk, timeout) @@ -78,23 +78,23 @@ def signal(signal, content="", mac_method="", mac="", reqcbk=None, returned for a synch request but nothing will be returned for an asynch request. - @param signal: the signal that causes the callback to be launched - @type signal: int (see the `events.proto` file) - @param content: the contents of the event signal - @type content: str - @param mac_method: the method used to auth mac - @type mac_method: str - @param mac: the content of the auth mac - @type mac: str - @param reqcbk: a callback to be called when a response from server is + :param signal: the signal that causes the callback to be launched + :type signal: int (see the `events.proto` file) + :param content: the contents of the event signal + :type content: str + :param mac_method: the method used to auth mac + :type mac_method: str + :param mac: the content of the auth mac + :type mac: str + :param reqcbk: a callback to be called when a response from server is received - @type reqcbk: function + :type reqcbk: function callback(leap.common.events.events_pb2.EventResponse) - @param timeout: the timeout for synch calls - @type timeout: int + :param timeout: the timeout for synch calls + :type timeout: int - @return: the response from server for synch calls or nothing for asynch + :return: the response from server for synch calls or nothing for asynch calls - @rtype: leap.common.events.events_pb2.EventsResponse or None + :rtype: leap.common.events.events_pb2.EventsResponse or None """ return component.signal(signal, content, mac_method, mac, reqcbk, timeout) diff --git a/src/leap/common/events/component.py b/src/leap/common/events/component.py index 98a97bc..1669356 100644 --- a/src/leap/common/events/component.py +++ b/src/leap/common/events/component.py @@ -63,8 +63,8 @@ def ensure_component_daemon(): Ensure the component daemon is running and listening for incoming messages. - @return: the daemon instance - @rtype: EventsComponentDaemon + :return: the daemon instance + :rtype: EventsComponentDaemon """ import time daemon = EventsComponentDaemon.ensure(0) @@ -91,28 +91,28 @@ def register(signal, callback, uid=None, replace=False, reqcbk=None, returned for a synch request but nothing will be returned for an asynch request. - @param signal: the signal that causes the callback to be launched - @type signal: int (see the `events.proto` file) - @param callback: the callback to be called when the signal is received - @type callback: function + :param signal: the signal that causes the callback to be launched + :type signal: int (see the `events.proto` file) + :param callback: the callback to be called when the signal is received + :type callback: function callback(leap.common.events.events_pb2.SignalRequest) - @param uid: a unique id for the callback - @type uid: int - @param replace: should an existent callback with same uid be replaced? - @type replace: bool - @param reqcbk: a callback to be called when a response from server is + :param uid: a unique id for the callback + :type uid: int + :param replace: should an existent callback with same uid be replaced? + :type replace: bool + :param reqcbk: a callback to be called when a response from server is received - @type reqcbk: function + :type reqcbk: function callback(leap.common.events.events_pb2.EventResponse) - @param timeout: the timeout for synch calls - @type timeout: int + :param timeout: the timeout for synch calls + :type timeout: int Might raise a CallbackAlreadyRegistered exception if there's already a callback identified by the given uid and replace is False. - @return: the response from server for synch calls or nothing for asynch + :return: the response from server for synch calls or nothing for asynch calls - @rtype: leap.common.events.events_pb2.EventsResponse or None + :rtype: leap.common.events.events_pb2.EventsResponse or None """ ensure_component_daemon() # so we can receive registered signals # register callback locally @@ -152,24 +152,24 @@ def signal(signal, content="", mac_method="", mac="", reqcbk=None, returned for a synch request but nothing will be returned for an asynch request. - @param signal: the signal that causes the callback to be launched - @type signal: int (see the `events.proto` file) - @param content: the contents of the event signal - @type content: str - @param mac_method: the method used for auth mac - @type mac_method: str - @param mac: the content of the auth mac - @type mac: str - @param reqcbk: a callback to be called when a response from server is + :param signal: the signal that causes the callback to be launched + :type signal: int (see the `events.proto` file) + :param content: the contents of the event signal + :type content: str + :param mac_method: the method used for auth mac + :type mac_method: str + :param mac: the content of the auth mac + :type mac: str + :param reqcbk: a callback to be called when a response from server is received - @type reqcbk: function + :type reqcbk: function callback(leap.common.events.events_pb2.EventResponse) - @param timeout: the timeout for synch calls - @type timeout: int + :param timeout: the timeout for synch calls + :type timeout: int - @return: the response from server for synch calls or nothing for asynch + :return: the response from server for synch calls or nothing for asynch calls - @rtype: leap.common.events.events_pb2.EventsResponse or None + :rtype: leap.common.events.events_pb2.EventsResponse or None """ request = proto.SignalRequest() request.event = signal @@ -197,12 +197,12 @@ class EventsComponentService(proto.EventsComponentService): This method is called whenever a signal request is received from server. - @param controller: used to mediate a single method call - @type controller: protobuf.socketrpc.controller.SocketRpcController - @param request: the request received from the component - @type request: leap.common.events.events_pb2.SignalRequest - @param done: callback to be called when done - @type done: protobuf.socketrpc.server.Callback + :param controller: used to mediate a single method call + :type controller: protobuf.socketrpc.controller.SocketRpcController + :param request: the request received from the component + :type request: leap.common.events.events_pb2.SignalRequest + :param done: callback to be called when done + :type done: protobuf.socketrpc.server.Callback """ logger.info('Received signal from server: %s' % str(request)) @@ -230,10 +230,10 @@ class EventsComponentDaemon(daemon.EventsSingletonDaemon): """ Make sure the daemon is running on the given port. - @param port: the port in which the daemon should listen - @type port: int + :param port: the port in which the daemon should listen + :type port: int - @return: a daemon instance - @rtype: EventsComponentDaemon + :return: a daemon instance + :rtype: EventsComponentDaemon """ return cls.ensure_service(port, EventsComponentService()) diff --git a/src/leap/common/events/daemon.py b/src/leap/common/events/daemon.py index d2c7b9b..c253948 100644 --- a/src/leap/common/events/daemon.py +++ b/src/leap/common/events/daemon.py @@ -50,10 +50,10 @@ class EventsRpcServer(SocketRpcServer): """ Initialize a RPC server. - @param port: the port in which to listen for incoming messages - @type port: int - @param host: the address to bind to - @type host: str + :param port: the port in which to listen for incoming messages + :type port: int + :param host: the address to bind to + :type host: str """ SocketRpcServer.__init__(self, port, host) self._server = None @@ -111,10 +111,10 @@ class EventsSingletonDaemon(threading.Thread): This is a static method disguised as instance method that actually does the initialization of the daemon instance. - @param port: the port in which to listen for incoming messages - @type port: int - @param service: the service to provide in this daemon - @type service: google.protobuf.service.Service + :param port: the port in which to listen for incoming messages + :type port: int + :param service: the service to provide in this daemon + :type service: google.protobuf.service.Service """ threading.Thread.__init__(self) self._port = port @@ -141,11 +141,11 @@ class EventsSingletonDaemon(threading.Thread): with the appropriate service from the `events.proto` definitions, and return the daemon instance. - @param port: the port in which the daemon should be listening - @type port: int + :param port: the port in which the daemon should be listening + :type port: int - @return: a daemon instance - @rtype: EventsSingletonDaemon + :return: a daemon instance + :rtype: EventsSingletonDaemon """ raise NotImplementedError(self.ensure) @@ -156,11 +156,11 @@ class EventsSingletonDaemon(threading.Thread): Might return ServiceAlreadyRunningException - @param port: the port in which the daemon should be listening - @type port: int + :param port: the port in which the daemon should be listening + :type port: int - @return: a daemon instance - @rtype: EventsSingletonDaemon + :return: a daemon instance + :rtype: EventsSingletonDaemon """ daemon = cls(port, service) if not daemon.is_alive(): @@ -178,8 +178,8 @@ class EventsSingletonDaemon(threading.Thread): """ Retrieve singleton instance of this daemon. - @return: a daemon instance - @rtype: EventsSingletonDaemon + :return: a daemon instance + :rtype: EventsSingletonDaemon """ return cls.__instance @@ -200,8 +200,8 @@ class EventsSingletonDaemon(threading.Thread): Retrieve the value of the port to which the service running in this daemon is binded to. - @return: the port to which the daemon is binded to - @rtype: int + :return: the port to which the daemon is binded to + :rtype: int """ if self._port is 0: self._port = self._server.port diff --git a/src/leap/common/events/server.py b/src/leap/common/events/server.py index 16c6513..33ba580 100644 --- a/src/leap/common/events/server.py +++ b/src/leap/common/events/server.py @@ -55,11 +55,11 @@ def ensure_server(port=SERVER_PORT): Attempt to connect to given local port. Upon success, assume that the events server has already been started. Upon failure, start events server. - @param port: the port in which server should be listening - @type port: int + :param port: the port in which server should be listening + :type port: int - @return: the daemon instance or nothing - @rtype: EventsServerDaemon or None + :return: the daemon instance or nothing + :rtype: EventsServerDaemon or None """ try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -81,12 +81,12 @@ class EventsServerService(proto.EventsServerService): """ Register a component port to be signaled when specific events come in. - @param controller: used to mediate a single method call - @type controller: protobuf.socketrpc.controller.SocketRpcController - @param request: the request received from the component - @type request: leap.common.events.events_pb2.RegisterRequest - @param done: callback to be called when done - @type done: protobuf.socketrpc.server.Callback + :param controller: used to mediate a single method call + :type controller: protobuf.socketrpc.controller.SocketRpcController + :param request: the request received from the component + :type request: leap.common.events.events_pb2.RegisterRequest + :param done: callback to be called when done + :type done: protobuf.socketrpc.server.Callback """ logger.info("Received registration request: %s" % str(request)) # add component port to signal list @@ -105,12 +105,12 @@ class EventsServerService(proto.EventsServerService): Perform an RPC call to signal all components registered to receive a specific signal. - @param controller: used to mediate a single method call - @type controller: protobuf.socketrpc.controller.SocketRpcController - @param request: the request received from the component - @type request: leap.common.events.events_pb2.SignalRequest - @param done: callback to be called when done - @type done: protobuf.socketrpc.server.Callback + :param controller: used to mediate a single method call + :type controller: protobuf.socketrpc.controller.SocketRpcController + :param request: the request received from the component + :type request: leap.common.events.events_pb2.SignalRequest + :param done: callback to be called when done + :type done: protobuf.socketrpc.server.Callback """ logger.info('Received signal from component: %s', str(request)) # send signal to all registered components @@ -140,10 +140,10 @@ class EventsServerDaemon(daemon.EventsSingletonDaemon): """ Make sure the daemon is running on the given port. - @param port: the port in which the daemon should listen - @type port: int + :param port: the port in which the daemon should listen + :type port: int - @return: a daemon instance - @rtype: EventsServerDaemon + :return: a daemon instance + :rtype: EventsServerDaemon """ return cls.ensure_service(port, EventsServerService()) diff --git a/src/leap/common/files.py b/src/leap/common/files.py index 4c443dd..bba281b 100644 --- a/src/leap/common/files.py +++ b/src/leap/common/files.py @@ -33,8 +33,8 @@ def check_and_fix_urw_only(cert): Might raise OSError - @param cert: Certificate path - @type cert: str + :param cert: Certificate path + :type cert: str """ mode = stat.S_IMODE(os.stat(cert).st_mode) @@ -53,10 +53,10 @@ def get_mtime(filename): """ Returns the modified time or None if the file doesn't exist - @param filename: path to check - @type filename: str + :param filename: path to check + :type filename: str - @rtype: str + :rtype: str """ try: mtime = time.ctime(os.path.getmtime(filename)) + " GMT" @@ -72,8 +72,8 @@ def mkdir_p(path): Might raise OSError - @param path: path to create - @type path: str + :param path: path to create + :type path: str """ try: os.makedirs(path) @@ -97,14 +97,14 @@ def which(name, flags=os.X_OK, path_extension="/usr/sbin:/sbin"): On MS-Windows the only flag that has any meaning is os.F_OK. Any other flags will be ignored. - @type name: C{str} - @param name: The name for which to search. + :type name: C{str} + :param name: The name for which to search. - @type flags: C{int} - @param flags: Arguments to L{os.access}. + :type flags: C{int} + :param flags: Arguments to L{os.access}. - @rtype: C{list} - @param: A list of the full paths to files found, in the + :rtype: C{list} + :param: A list of the full paths to files found, in the order in which they were found. """ diff --git a/src/leap/common/keymanager/__init__.py b/src/leap/common/keymanager/__init__.py index 7aaeddf..f5c5f5e 100644 --- a/src/leap/common/keymanager/__init__.py +++ b/src/leap/common/keymanager/__init__.py @@ -73,22 +73,22 @@ class KeyManager(object): Initialize a Key Manager for user's C{address} with provider's nickserver reachable in C{url}. - @param address: The address of the user of this Key Manager. - @type address: str - @param url: The URL of the nickserver. - @type url: str - @param soledad: A Soledad instance for local storage of keys. - @type soledad: leap.soledad.Soledad - @param session_id: The session ID for interacting with the webapp API. - @type session_id: str - @param ca_cert_path: The path to the CA certificate. - @type ca_cert_path: str - @param api_uri: The URI of the webapp API. - @type api_uri: str - @param api_version: The version of the webapp API. - @type api_version: str - @param uid: The users' UID. - @type uid: str + :param address: The address of the user of this Key Manager. + :type address: str + :param url: The URL of the nickserver. + :type url: str + :param soledad: A Soledad instance for local storage of keys. + :type soledad: leap.soledad.Soledad + :param session_id: The session ID for interacting with the webapp API. + :type session_id: str + :param ca_cert_path: The path to the CA certificate. + :type ca_cert_path: str + :param api_uri: The URI of the webapp API. + :type api_uri: str + :param api_version: The version of the webapp API. + :type api_version: str + :param uid: The users' UID. + :type uid: str """ self._address = address self._nickserver_uri = nickserver_uri @@ -145,13 +145,13 @@ class KeyManager(object): """ Send a GET request to C{uri} containing C{data}. - @param uri: The URI of the request. - @type uri: str - @param data: The body of the request. - @type data: dict, str or file + :param uri: The URI of the request. + :type uri: str + :param data: The body of the request. + :type data: dict, str or file - @return: The response to the request. - @rtype: requests.Response + :return: The response to the request. + :rtype: requests.Response """ leap_assert( self._ca_cert_path is not None, @@ -172,13 +172,13 @@ class KeyManager(object): verify the server certificate and the configured session id for authentication. - @param uri: The URI of the request. - @type uri: str - @param data: The body of the request. - @type data: dict, str or file + :param uri: The URI of the request. + :type uri: str + :param data: The body of the request. + :type data: dict, str or file - @return: The response to the request. - @rtype: requests.Response + :return: The response to the request. + :rtype: requests.Response """ leap_assert( self._ca_cert_path is not None, @@ -198,8 +198,8 @@ class KeyManager(object): Fetch keys bound to C{address} from nickserver and insert them in local database. - @param address: The address bound to the keys. - @type address: str + :param address: The address bound to the keys. + :type address: str @raise KeyNotFound: If the key was not found on nickserver. """ @@ -228,8 +228,8 @@ class KeyManager(object): will be saved in the server in a way it is publicly retrievable through the hash string. - @param ktype: The type of the key. - @type ktype: KeyType + :param ktype: The type of the key. + :type ktype: KeyType @raise KeyNotFound: If the key was not found in local database. """ @@ -255,15 +255,15 @@ class KeyManager(object): First, search for the key in local storage. If it is not available, then try to fetch from nickserver. - @param address: The address bound to the key. - @type address: str - @param ktype: The type of the key. - @type ktype: KeyType - @param private: Look for a private key instead of a public one? - @type private: bool + :param address: The address bound to the key. + :type address: str + :param ktype: The type of the key. + :type ktype: KeyType + :param private: Look for a private key instead of a public one? + :type private: bool - @return: A key of type C{ktype} bound to C{address}. - @rtype: EncryptionKey + :return: A key of type C{ktype} bound to C{address}. + :rtype: EncryptionKey @raise KeyNotFound: If the key was not found both locally and in keyserver. """ @@ -285,8 +285,8 @@ class KeyManager(object): """ Return all keys stored in local database. - @return: A list with all keys in local db. - @rtype: list + :return: A list with all keys in local db. + :rtype: list """ return map( lambda doc: build_key_from_dict( @@ -315,11 +315,11 @@ class KeyManager(object): """ Generate a key of type C{ktype} bound to the user's address. - @param ktype: The type of the key. - @type ktype: KeyType + :param ktype: The type of the key. + :type ktype: KeyType - @return: The generated key. - @rtype: EncryptionKey + :return: The generated key. + :rtype: EncryptionKey """ return self._wrapper_map[ktype].gen_key(self._address) diff --git a/src/leap/common/keymanager/gpg.py b/src/leap/common/keymanager/gpg.py index f3e6453..15c1d9f 100644 --- a/src/leap/common/keymanager/gpg.py +++ b/src/leap/common/keymanager/gpg.py @@ -43,8 +43,8 @@ class ListPackets(): """ Initialize the packet listing handling class. - @param gpg: GPG object instance. - @type gpg: gnupg.GPG + :param gpg: GPG object instance. + :type gpg: gnupg.GPG """ self.gpg = gpg self.nodata = None @@ -57,10 +57,10 @@ class ListPackets(): """ Handle one line of the --list-packets status message. - @param key: The status message key. - @type key: str - @param value: The status message value. - @type value: str + :param key: The status message key. + :type key: str + :param value: The status message value. + :type value: str """ # TODO: write tests for handle_status if key == 'NODATA': @@ -91,21 +91,21 @@ class GPGWrapper(gnupg.GPG): """ Initialize a GnuPG process wrapper. - @param gpgbinary: Name for GnuPG binary executable. - @type gpgbinary: C{str} - @param gpghome: Full pathname to directory containing the public and + :param gpgbinary: Name for GnuPG binary executable. + :type gpgbinary: C{str} + :param gpghome: Full pathname to directory containing the public and private keyrings. - @type gpghome: C{str} - @param keyring: Name of alternative keyring file to use. If specified, + :type gpghome: C{str} + :param keyring: Name of alternative keyring file to use. If specified, the default keyring is not used. - @param verbose: Should some verbose info be output? - @type verbose: bool - @param use_agent: Should pass `--use-agent` to GPG binary? - @type use_agent: bool - @param keyring: Path for the keyring to use. - @type keyring: str + :param verbose: Should some verbose info be output? + :type verbose: bool + :param use_agent: Should pass `--use-agent` to GPG binary? + :type use_agent: bool + :param keyring: Path for the keyring to use. + :type keyring: str @options: A list of additional options to pass to the GPG binary. - @type options: list + :type options: list @raise: RuntimeError with explanation message if there is a problem invoking gpg. @@ -119,13 +119,13 @@ class GPGWrapper(gnupg.GPG): """ Find user's key based on their email. - @param email: Email address of key being searched for. - @type email: str - @param secret: Should we search for a secret key? - @type secret: bool + :param email: Email address of key being searched for. + :type email: str + :param secret: Should we search for a secret key? + :type secret: bool - @return: The fingerprint of the found key. - @rtype: str + :return: The fingerprint of the found key. + :rtype: str """ for key in self.list_keys(secret=secret): for uid in key['uids']: @@ -137,13 +137,13 @@ class GPGWrapper(gnupg.GPG): """ Find user's key based on a subkey fingerprint. - @param email: Subkey fingerprint of the key being searched for. - @type email: str - @param secret: Should we search for a secret key? - @type secret: bool + :param email: Subkey fingerprint of the key being searched for. + :type email: str + :param secret: Should we search for a secret key? + :type secret: bool - @return: The fingerprint of the found key. - @rtype: str + :return: The fingerprint of the found key. + :rtype: str """ for key in self.list_keys(secret=secret): for sub in key['subkeys']: @@ -156,13 +156,13 @@ class GPGWrapper(gnupg.GPG): """ Find user's key based on the key ID. - @param email: The key ID of the key being searched for. - @type email: str - @param secret: Should we search for a secret key? - @type secret: bool + :param email: The key ID of the key being searched for. + :type email: str + :param secret: Should we search for a secret key? + :type secret: bool - @return: The fingerprint of the found key. - @rtype: str + :return: The fingerprint of the found key. + :rtype: str """ for key in self.list_keys(secret=secret): if keyid == key['keyid']: @@ -174,13 +174,13 @@ class GPGWrapper(gnupg.GPG): """ Find user's key based on the key fingerprint. - @param email: The fingerprint of the key being searched for. - @type email: str - @param secret: Should we search for a secret key? - @type secret: bool + :param email: The fingerprint of the key being searched for. + :type email: str + :param secret: Should we search for a secret key? + :type secret: bool - @return: The fingerprint of the found key. - @rtype: str + :return: The fingerprint of the found key. + :rtype: str """ for key in self.list_keys(secret=secret): if fingerprint == key['fingerprint']: @@ -193,23 +193,23 @@ class GPGWrapper(gnupg.GPG): """ Encrypt data using GPG. - @param data: The data to be encrypted. - @type data: str - @param recipient: The address of the public key to be used. - @type recipient: str - @param sign: Should the encrypted content be signed? - @type sign: bool - @param always_trust: Skip key validation and assume that used keys + :param data: The data to be encrypted. + :type data: str + :param recipient: The address of the public key to be used. + :type recipient: str + :param sign: Should the encrypted content be signed? + :type sign: bool + :param always_trust: Skip key validation and assume that used keys are always fully trusted? - @type always_trust: bool - @param passphrase: The passphrase to be used if symmetric encryption + :type always_trust: bool + :param passphrase: The passphrase to be used if symmetric encryption is desired. - @type passphrase: str - @param symmetric: Should we encrypt to a password? - @type symmetric: bool + :type passphrase: str + :param symmetric: Should we encrypt to a password? + :type symmetric: bool - @return: An object with encrypted result in the `data` field. - @rtype: gnupg.Crypt + :return: An object with encrypted result in the `data` field. + :rtype: gnupg.Crypt """ # TODO: devise a way so we don't need to "always trust". return gnupg.GPG.encrypt(self, data, recipient, sign=sign, @@ -222,17 +222,17 @@ class GPGWrapper(gnupg.GPG): """ Decrypt data using GPG. - @param data: The data to be decrypted. - @type data: str - @param always_trust: Skip key validation and assume that used keys + :param data: The data to be decrypted. + :type data: str + :param always_trust: Skip key validation and assume that used keys are always fully trusted? - @type always_trust: bool - @param passphrase: The passphrase to be used if symmetric encryption + :type always_trust: bool + :param passphrase: The passphrase to be used if symmetric encryption is desired. - @type passphrase: str + :type passphrase: str - @return: An object with decrypted result in the `data` field. - @rtype: gnupg.Crypt + :return: An object with decrypted result in the `data` field. + :rtype: gnupg.Crypt """ # TODO: devise a way so we don't need to "always trust". return gnupg.GPG.decrypt(self, data, always_trust=always_trust, @@ -242,13 +242,13 @@ class GPGWrapper(gnupg.GPG): """ Send keys to a keyserver - @param keyserver: The keyserver to send the keys to. - @type keyserver: str - @param keyids: The key ids to send. - @type keyids: list + :param keyserver: The keyserver to send the keys to. + :type keyserver: str + :param keyids: The key ids to send. + :type keyids: list - @return: A list of keys sent to server. - @rtype: gnupg.ListKeys + :return: A list of keys sent to server. + :rtype: gnupg.ListKeys """ # TODO: write tests for this. # TODO: write a SendKeys class to handle status for this. @@ -269,29 +269,29 @@ class GPGWrapper(gnupg.GPG): """ Encrypt the message read from the file-like object 'file'. - @param file: The file to be encrypted. - @type data: file - @param recipient: The address of the public key to be used. - @type recipient: str - @param sign: Should the encrypted content be signed? - @type sign: bool - @param always_trust: Skip key validation and assume that used keys + :param file: The file to be encrypted. + :type data: file + :param recipient: The address of the public key to be used. + :type recipient: str + :param sign: Should the encrypted content be signed? + :type sign: bool + :param always_trust: Skip key validation and assume that used keys are always fully trusted? - @type always_trust: bool - @param passphrase: The passphrase to be used if symmetric encryption + :type always_trust: bool + :param passphrase: The passphrase to be used if symmetric encryption is desired. - @type passphrase: str - @param armor: Create ASCII armored output? - @type armor: bool - @param output: Path of file to write results in. - @type output: str - @param symmetric: Should we encrypt to a password? - @type symmetric: bool - @param cipher_algo: Algorithm to use. - @type cipher_algo: str - - @return: An object with encrypted result in the `data` field. - @rtype: gnupg.Crypt + :type passphrase: str + :param armor: Create ASCII armored output? + :type armor: bool + :param output: Path of file to write results in. + :type output: str + :param symmetric: Should we encrypt to a password? + :type symmetric: bool + :param cipher_algo: Algorithm to use. + :type cipher_algo: str + + :return: An object with encrypted result in the `data` field. + :rtype: gnupg.Crypt """ args = ['--encrypt'] if symmetric: @@ -323,11 +323,11 @@ class GPGWrapper(gnupg.GPG): """ List the sequence of packets. - @param data: The data to extract packets from. - @type data: str + :param data: The data to extract packets from. + :type data: str - @return: An object with packet info. - @rtype ListPackets + :return: An object with packet info. + :rtype ListPackets """ args = ["--list-packets"] result = self.result_map['list-packets'](self) @@ -342,11 +342,11 @@ class GPGWrapper(gnupg.GPG): """ Return the key to which data is encrypted to. - @param data: The data to be examined. - @type data: str + :param data: The data to be examined. + :type data: str - @return: The fingerprint of the key to which data is encrypted to. - @rtype: str + :return: The fingerprint of the key to which data is encrypted to. + :rtype: str """ # TODO: make this support multiple keys. result = self.list_packets(data) @@ -362,11 +362,11 @@ class GPGWrapper(gnupg.GPG): """ Say whether some chunk of data is encrypted to a symmetric key. - @param data: The data to be examined. - @type data: str + :param data: The data to be examined. + :type data: str - @return: Whether data is encrypted to a symmetric key. - @rtype: bool + :return: Whether data is encrypted to a symmetric key. + :rtype: bool """ result = self.list_packets(data) return bool(result.need_passphrase_sym) @@ -375,11 +375,11 @@ class GPGWrapper(gnupg.GPG): """ Say whether some chunk of data is encrypted to a private key. - @param data: The data to be examined. - @type data: str + :param data: The data to be examined. + :type data: str - @return: Whether data is encrypted to a private key. - @rtype: bool + :return: Whether data is encrypted to a private key. + :rtype: bool """ result = self.list_packets(data) return bool(result.key) @@ -388,10 +388,10 @@ class GPGWrapper(gnupg.GPG): """ Say whether some chunk of data is encrypted to a key. - @param data: The data to be examined. - @type data: str + :param data: The data to be examined. + :type data: str - @return: Whether data is encrypted to a key. - @rtype: bool + :return: Whether data is encrypted to a key. + :rtype: bool """ return self.is_encrypted_asym(data) or self.is_encrypted_sym(data) diff --git a/src/leap/common/keymanager/keys.py b/src/leap/common/keymanager/keys.py index 1d87858..f2c1beb 100644 --- a/src/leap/common/keymanager/keys.py +++ b/src/leap/common/keymanager/keys.py @@ -65,10 +65,10 @@ def is_address(address): """ Return whether the given C{address} is in the form user@provider. - @param address: The address to be tested. - @type address: str - @return: Whether C{address} is in the form user@provider. - @rtype: bool + :param address: The address to be tested. + :type address: str + :return: Whether C{address} is in the form user@provider. + :rtype: bool """ return bool(re.match('[\w.-]+@[\w.-]+', address)) @@ -77,12 +77,12 @@ def build_key_from_dict(kClass, address, kdict): """ Build an C{kClass} key bound to C{address} based on info in C{kdict}. - @param address: The address bound to the key. - @type address: str - @param kdict: Dictionary with key data. - @type kdict: dict - @return: An instance of the key. - @rtype: C{kClass} + :param address: The address bound to the key. + :type address: str + :param kdict: Dictionary with key data. + :type kdict: dict + :return: An instance of the key. + :rtype: C{kClass} """ leap_assert( address == kdict[KEY_ADDRESS_KEY], @@ -106,15 +106,15 @@ def keymanager_doc_id(ktype, address, private=False): Return the document id for the document containing a key for C{address}. - @param address: The type of the key. - @type address: KeyType - @param address: The address bound to the key. - @type address: str - @param private: Whether the key is private or not. - @type private: bool - @return: The document id for the document that stores a key bound to + :param address: The type of the key. + :type address: KeyType + :param address: The address bound to the key. + :type address: str + :param private: Whether the key is private or not. + :type private: bool + :return: The document id for the document that stores a key bound to C{address}. - @rtype: str + :rtype: str """ leap_assert(is_address(address), "Wrong address format: %s" % address) ktype = str(ktype) @@ -163,8 +163,8 @@ class EncryptionKey(object): """ Return a JSON string describing this key. - @return: The JSON string describing this key. - @rtype: str + :return: The JSON string describing this key. + :rtype: str """ return json.dumps({ KEY_ADDRESS_KEY: self.address, @@ -211,8 +211,8 @@ class EncryptionScheme(object): """ Initialize this Encryption Scheme. - @param soledad: A Soledad instance for local storage of keys. - @type soledad: leap.soledad.Soledad + :param soledad: A Soledad instance for local storage of keys. + :type soledad: leap.soledad.Soledad """ self._soledad = soledad @@ -221,13 +221,13 @@ class EncryptionScheme(object): """ Get key from local storage. - @param address: The address bound to the key. - @type address: str - @param private: Look for a private key instead of a public one? - @type private: bool + :param address: The address bound to the key. + :type address: str + :param private: Look for a private key instead of a public one? + :type private: bool - @return: The key bound to C{address}. - @rtype: EncryptionKey + :return: The key bound to C{address}. + :rtype: EncryptionKey @raise KeyNotFound: If the key was not found on local storage. """ pass @@ -237,8 +237,8 @@ class EncryptionScheme(object): """ Put a key in local storage. - @param key: The key to be stored. - @type key: EncryptionKey + :param key: The key to be stored. + :type key: EncryptionKey """ pass @@ -247,11 +247,11 @@ class EncryptionScheme(object): """ Generate a new key. - @param address: The address bound to the key. - @type address: str + :param address: The address bound to the key. + :type address: str - @return: The key bound to C{address}. - @rtype: EncryptionKey + :return: The key bound to C{address}. + :rtype: EncryptionKey """ pass @@ -260,7 +260,7 @@ class EncryptionScheme(object): """ Remove C{key} from storage. - @param key: The key to be removed. - @type key: EncryptionKey + :param key: The key to be removed. + :type key: EncryptionKey """ pass diff --git a/src/leap/common/keymanager/openpgp.py b/src/leap/common/keymanager/openpgp.py index d53afd6..b08bff6 100644 --- a/src/leap/common/keymanager/openpgp.py +++ b/src/leap/common/keymanager/openpgp.py @@ -48,11 +48,11 @@ def temporary_gpgwrapper(keys=None): Returns a unitary gpg wrapper that implements context manager protocol. - @param key_data: ASCII armored key data. - @type key_data: str + :param key_data: ASCII armored key data. + :type key_data: str - @return: a GPGWrapper instance - @rtype: GPGWrapper + :return: a GPGWrapper instance + :rtype: GPGWrapper """ # TODO do here checks on key_data return TempGPGWrapper(keys=keys) @@ -168,8 +168,8 @@ class TempGPGWrapper(object): """ def __init__(self, keys=None): """ - @param keys: OpenPGP key, or list of. - @type keys: OpenPGPKey or list of OpenPGPKeys + :param keys: OpenPGP key, or list of. + :type keys: OpenPGPKey or list of OpenPGPKeys """ self._gpg = None if not keys: @@ -184,8 +184,8 @@ class TempGPGWrapper(object): """ Calls the unitary gpgwrapper initializer - @return: A GPG wrapper with a unitary keyring. - @rtype: gnupg.GPG + :return: A GPG wrapper with a unitary keyring. + :rtype: gnupg.GPG """ self._build_keyring() return self._gpg @@ -201,11 +201,11 @@ class TempGPGWrapper(object): """ Create an empty GPG keyring and import C{keys} into it. - @param keys: List of keys to add to the keyring. - @type keys: list of OpenPGPKey + :param keys: List of keys to add to the keyring. + :type keys: list of OpenPGPKey - @return: A GPG wrapper with a unitary keyring. - @rtype: gnupg.GPG + :return: A GPG wrapper with a unitary keyring. + :rtype: gnupg.GPG """ privkeys = [key for key in self._keys if key and key.private is True] publkeys = [key for key in self._keys if key and key.private is False] @@ -275,15 +275,15 @@ def encrypt_asym(data, key, passphrase=None, sign=None): """ Encrypt C{data} using public @{key} and sign with C{sign} key. - @param data: The data to be encrypted. - @type data: str - @param pubkey: The key used to encrypt. - @type pubkey: OpenPGPKey - @param sign: The key used for signing. - @type sign: OpenPGPKey + :param data: The data to be encrypted. + :type data: str + :param pubkey: The key used to encrypt. + :type pubkey: OpenPGPKey + :param sign: The key used for signing. + :type sign: OpenPGPKey - @return: The encrypted data. - @rtype: str + :return: The encrypted data. + :rtype: str """ leap_assert_type(key, OpenPGPKey) leap_assert(key.private is False, 'Key is not public.') @@ -307,15 +307,15 @@ def decrypt_asym(data, key, passphrase=None, verify=None): """ Decrypt C{data} using private @{key} and verify with C{verify} key. - @param data: The data to be decrypted. - @type data: str - @param privkey: The key used to decrypt. - @type privkey: OpenPGPKey - @param verify: The key used to verify a signature. - @type verify: OpenPGPKey + :param data: The data to be decrypted. + :type data: str + :param privkey: The key used to decrypt. + :type privkey: OpenPGPKey + :param verify: The key used to verify a signature. + :type verify: OpenPGPKey - @return: The decrypted data. - @rtype: str + :return: The decrypted data. + :rtype: str @raise InvalidSignature: Raised if unable to verify the signature with C{verify} key. @@ -334,11 +334,11 @@ def is_encrypted(data): """ Return whether C{data} was encrypted using OpenPGP. - @param data: The data we want to know about. - @type data: str + :param data: The data we want to know about. + :type data: str - @return: Whether C{data} was encrypted using this wrapper. - @rtype: bool + :return: Whether C{data} was encrypted using this wrapper. + :rtype: bool """ return lambda gpg: gpg.is_encrypted(data) @@ -348,11 +348,11 @@ def is_encrypted_asym(data): """ Return whether C{data} was asymmetrically encrypted using OpenPGP. - @param data: The data we want to know about. - @type data: str + :param data: The data we want to know about. + :type data: str - @return: Whether C{data} was encrypted using this wrapper. - @rtype: bool + :return: Whether C{data} was encrypted using this wrapper. + :rtype: bool """ return lambda gpg: gpg.is_encrypted_asym(data) @@ -362,14 +362,14 @@ def sign(data, privkey): """ Sign C{data} with C{privkey}. - @param data: The data to be signed. - @type data: str + :param data: The data to be signed. + :type data: str - @param privkey: The private key to be used to sign. - @type privkey: OpenPGPKey + :param privkey: The private key to be used to sign. + :type privkey: OpenPGPKey - @return: The ascii-armored signed data. - @rtype: str + :return: The ascii-armored signed data. + :rtype: str """ leap_assert_type(privkey, OpenPGPKey) leap_assert(privkey.private is True) @@ -384,14 +384,14 @@ def verify(data, key): """ Verify signed C{data} with C{pubkey}. - @param data: The data to be verified. - @type data: str + :param data: The data to be verified. + :type data: str - @param pubkey: The public key to be used on verification. - @type pubkey: OpenPGPKey + :param pubkey: The public key to be used on verification. + :type pubkey: OpenPGPKey - @return: The ascii-armored signed data. - @rtype: str + :return: The ascii-armored signed data. + :rtype: str """ leap_assert_type(key, OpenPGPKey) leap_assert(key.private is False) @@ -412,14 +412,14 @@ def _build_key_from_gpg(address, key, key_data): ASCII armored GPG key data has to be queried independently in this wrapper, so we receive it in C{key_data}. - @param address: The address bound to the key. - @type address: str - @param key: Key obtained from GPG storage. - @type key: dict - @param key_data: Key data obtained from GPG storage. - @type key_data: str - @return: An instance of the key. - @rtype: OpenPGPKey + :param address: The address bound to the key. + :type address: str + :param key: Key obtained from GPG storage. + :type key: dict + :param key_data: Key data obtained from GPG storage. + :type key_data: str + :return: An instance of the key. + :rtype: OpenPGPKey """ return OpenPGPKey( address, @@ -452,8 +452,8 @@ class OpenPGPScheme(EncryptionScheme): """ Initialize the OpenPGP wrapper. - @param soledad: A Soledad instance for key storage. - @type soledad: leap.soledad.Soledad + :param soledad: A Soledad instance for key storage. + :type soledad: leap.soledad.Soledad """ EncryptionScheme.__init__(self, soledad) @@ -461,10 +461,10 @@ class OpenPGPScheme(EncryptionScheme): """ Generate an OpenPGP keypair bound to C{address}. - @param address: The address bound to the key. - @type address: str - @return: The key bound to C{address}. - @rtype: OpenPGPKey + :param address: The address bound to the key. + :type address: str + :return: The key bound to C{address}. + :rtype: OpenPGPKey @raise KeyAlreadyExists: If key already exists in local database. """ # make sure the key does not already exist @@ -513,13 +513,13 @@ class OpenPGPScheme(EncryptionScheme): """ Get key bound to C{address} from local storage. - @param address: The address bound to the key. - @type address: str - @param private: Look for a private key instead of a public one? - @type private: bool + :param address: The address bound to the key. + :type address: str + :param private: Look for a private key instead of a public one? + :type private: bool - @return: The key bound to C{address}. - @rtype: OpenPGPKey + :return: The key bound to C{address}. + :rtype: OpenPGPKey @raise KeyNotFound: If the key was not found on local storage. """ leap_assert(is_address(address), 'Not an user address: %s' % address) @@ -532,8 +532,8 @@ class OpenPGPScheme(EncryptionScheme): """ Put key contained in ascii-armored C{key_data} in local storage. - @param key_data: The key data to be stored. - @type key_data: str + :param key_data: The key data to be stored. + :type key_data: str """ leap_assert_type(key_data, str) # TODO: add more checks for correct key data. @@ -583,8 +583,8 @@ class OpenPGPScheme(EncryptionScheme): """ Put C{key} in local storage. - @param key: The key to be stored. - @type key: OpenPGPKey + :param key: The key to be stored. + :type key: OpenPGPKey """ doc = self._get_key_doc(key.address, private=key.private) if doc is None: @@ -602,12 +602,12 @@ class OpenPGPScheme(EncryptionScheme): If C{private} is True, looks for a private key instead of a public. - @param address: The address bound to the key. - @type address: str - @param private: Whether to look for a private key. - @type private: bool - @return: The document with the key or None if it does not exist. - @rtype: leap.soledad.backends.leap_backend.LeapDocument + :param address: The address bound to the key. + :type address: str + :param private: Whether to look for a private key. + :type private: bool + :return: The document with the key or None if it does not exist. + :rtype: leap.soledad.backends.leap_backend.LeapDocument """ return self._soledad.get_doc( keymanager_doc_id(OpenPGPKey, address, private)) @@ -616,8 +616,8 @@ class OpenPGPScheme(EncryptionScheme): """ Remove C{key} from storage. - @param key: The key to be removed. - @type key: EncryptionKey + :param key: The key to be removed. + :type key: EncryptionKey """ leap_assert(key.__class__ is OpenPGPKey, 'Wrong key type.') stored_key = self.get_key(key.address, private=key.private) diff --git a/src/leap/common/testing/basetest.py b/src/leap/common/testing/basetest.py index 65e23a9..8890bf9 100644 --- a/src/leap/common/testing/basetest.py +++ b/src/leap/common/testing/basetest.py @@ -94,8 +94,8 @@ class BaseLeapTest(unittest.TestCase): Raises NotImplementedError for this platform if do_raise is True - @param do_raise: flag to actually raise exception - @type do_raise: bool + :param do_raise: flag to actually raise exception + :type do_raise: bool """ if do_raise: raise NotImplementedError( @@ -109,8 +109,8 @@ class BaseLeapTest(unittest.TestCase): prepending the temporal dir associated with this TestCase - @param filename: the filename - @type filename: str + :param filename: the filename + :type filename: str """ return os.path.join(self.tempdir, filename) @@ -119,8 +119,8 @@ class BaseLeapTest(unittest.TestCase): Touches a filepath, creating folders along the way if needed. - @param filepath: path to be touched - @type filepath: str + :param filepath: path to be touched + :type filepath: str """ folder, filename = os.path.split(filepath) if not os.path.isdir(folder): @@ -134,7 +134,7 @@ class BaseLeapTest(unittest.TestCase): """ Chmods 600 a file - @param filepath: filepath to be chmodded - @type filepath: str + :param filepath: filepath to be chmodded + :type filepath: str """ check_and_fix_urw_only(filepath) diff --git a/src/leap/common/testing/test_basetest.py b/src/leap/common/testing/test_basetest.py index 220e28d..cf0962d 100644 --- a/src/leap/common/testing/test_basetest.py +++ b/src/leap/common/testing/test_basetest.py @@ -38,8 +38,8 @@ class _TestCaseRunner(object): """ Runs a given TestCase - @param testcase: the testcase - @type testcase: unittest.TestCase + :param testcase: the testcase + :type testcase: unittest.TestCase """ if not testcase: return None -- cgit v1.2.3 From de1677894afd4dd85e9616d5cf58bfe86bd866e2 Mon Sep 17 00:00:00 2001 From: drebs Date: Wed, 29 May 2013 10:18:44 -0300 Subject: Remove keymanager from this repository. --- changes/feature_key-manager | 2 +- src/leap/common/keymanager/__init__.py | 341 --------------- src/leap/common/keymanager/errors.py | 86 ---- src/leap/common/keymanager/gpg.py | 397 ------------------ src/leap/common/keymanager/keys.py | 284 ------------- src/leap/common/keymanager/openpgp.py | 636 ---------------------------- src/leap/common/tests/test_keymanager.py | 686 ------------------------------- 7 files changed, 1 insertion(+), 2431 deletions(-) delete mode 100644 src/leap/common/keymanager/__init__.py delete mode 100644 src/leap/common/keymanager/errors.py delete mode 100644 src/leap/common/keymanager/gpg.py delete mode 100644 src/leap/common/keymanager/keys.py delete mode 100644 src/leap/common/keymanager/openpgp.py delete mode 100644 src/leap/common/tests/test_keymanager.py diff --git a/changes/feature_key-manager b/changes/feature_key-manager index 6588dde..47a62ed 100644 --- a/changes/feature_key-manager +++ b/changes/feature_key-manager @@ -1 +1 @@ - o Add a Key Manager. + o Move the Key Manager to leap client repository. diff --git a/src/leap/common/keymanager/__init__.py b/src/leap/common/keymanager/__init__.py deleted file mode 100644 index 9435cea..0000000 --- a/src/leap/common/keymanager/__init__.py +++ /dev/null @@ -1,341 +0,0 @@ -# -*- coding: utf-8 -*- -# __init__.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -""" -Key Manager is a Nicknym agent for LEAP client. -""" - -import requests - -try: - import simplejson as json -except ImportError: - import json # noqa - -from leap.common.check import leap_assert -from leap.common.keymanager.errors import ( - KeyNotFound, - NoPasswordGiven, -) -from leap.common.keymanager.keys import ( - build_key_from_dict, - KEYMANAGER_KEY_TAG, - TAGS_PRIVATE_INDEX, -) -from leap.common.keymanager.openpgp import ( - OpenPGPKey, - OpenPGPScheme, -) - - -# -# The Key Manager -# - -class KeyManager(object): - - # - # server's key storage constants - # - - OPENPGP_KEY = 'openpgp' - PUBKEY_KEY = "user[public_key]" - - def __init__(self, address, nickserver_uri, soledad, session_id=None, - ca_cert_path=None, api_uri=None, api_version=None, uid=None): - """ - Initialize a Key Manager for user's C{address} with provider's - nickserver reachable in C{url}. - - :param address: The address of the user of this Key Manager. - :type address: str - :param url: The URL of the nickserver. - :type url: str - :param soledad: A Soledad instance for local storage of keys. - :type soledad: leap.soledad.Soledad - :param session_id: The session ID for interacting with the webapp API. - :type session_id: str - :param ca_cert_path: The path to the CA certificate. - :type ca_cert_path: str - :param api_uri: The URI of the webapp API. - :type api_uri: str - :param api_version: The version of the webapp API. - :type api_version: str - :param uid: The users' UID. - :type uid: str - """ - self._address = address - self._nickserver_uri = nickserver_uri - self._soledad = soledad - self._session_id = session_id - self.ca_cert_path = ca_cert_path - self.api_uri = api_uri - self.api_version = api_version - self.uid = uid - # a dict to map key types to their handlers - self._wrapper_map = { - OpenPGPKey: OpenPGPScheme(soledad), - # other types of key will be added to this mapper. - } - # the following are used to perform https requests - self._fetcher = requests - self._session = self._fetcher.session() - - # - # utilities - # - - def _key_class_from_type(self, ktype): - """ - Return key class from string representation of key type. - """ - return filter( - lambda klass: str(klass) == ktype, - self._wrapper_map).pop() - - def _get(self, uri, data=None): - """ - Send a GET request to C{uri} containing C{data}. - - :param uri: The URI of the request. - :type uri: str - :param data: The body of the request. - :type data: dict, str or file - - :return: The response to the request. - :rtype: requests.Response - """ - leap_assert( - self._ca_cert_path is not None, - 'We need the CA certificate path!') - res = self._fetcher.get(uri, data=data, verify=self._ca_cert_path) - # assert that the response is valid - res.raise_for_status() - leap_assert( - res.headers['content-type'].startswith('application/json'), - 'Content-type is not JSON.') - return res - - def _put(self, uri, data=None): - """ - Send a PUT request to C{uri} containing C{data}. - - The request will be sent using the configured CA certificate path to - verify the server certificate and the configured session id for - authentication. - - :param uri: The URI of the request. - :type uri: str - :param data: The body of the request. - :type data: dict, str or file - - :return: The response to the request. - :rtype: requests.Response - """ - leap_assert( - self._ca_cert_path is not None, - 'We need the CA certificate path!') - leap_assert( - self._session_id is not None, - 'We need a session_id to interact with webapp!') - res = self._fetcher.put( - uri, data=data, verify=self._ca_cert_path, - cookies={'_session_id': self._session_id}) - # assert that the response is valid - res.raise_for_status() - return res - - def _fetch_keys_from_server(self, address): - """ - Fetch keys bound to C{address} from nickserver and insert them in - local database. - - :param address: The address bound to the keys. - :type address: str - - @raise KeyNotFound: If the key was not found on nickserver. - """ - # request keys from the nickserver - server_keys = self._get( - self._nickserver_uri, {'address': address}).json() - # insert keys in local database - if self.OPENPGP_KEY in server_keys: - self._wrapper_map[OpenPGPKey].put_ascii_key( - server_keys['openpgp']) - - # - # key management - # - - def send_key(self, ktype): - """ - Send user's key of type C{ktype} to provider. - - Public key bound to user's is sent to provider, which will sign it and - replace any prior keys for the same address in its database. - - If C{send_private} is True, then the private key is encrypted with - C{password} and sent to server in the same request, together with a - hash string of user's address and password. The encrypted private key - will be saved in the server in a way it is publicly retrievable - through the hash string. - - :param ktype: The type of the key. - :type ktype: KeyType - - @raise KeyNotFound: If the key was not found in local database. - """ - leap_assert( - ktype is OpenPGPKey, - 'For now we only know how to send OpenPGP public keys.') - # prepare the public key bound to address - pubkey = self.get_key( - self._address, ktype, private=False, fetch_remote=False) - data = { - self.PUBKEY_KEY: pubkey.key_data - } - uri = "%s/%s/users/%s.json" % ( - self._api_uri, - self._api_version, - self._uid) - self._put(uri, data) - - def get_key(self, address, ktype, private=False, fetch_remote=True): - """ - Return a key of type C{ktype} bound to C{address}. - - First, search for the key in local storage. If it is not available, - then try to fetch from nickserver. - - :param address: The address bound to the key. - :type address: str - :param ktype: The type of the key. - :type ktype: KeyType - :param private: Look for a private key instead of a public one? - :type private: bool - - :return: A key of type C{ktype} bound to C{address}. - :rtype: EncryptionKey - @raise KeyNotFound: If the key was not found both locally and in - keyserver. - """ - leap_assert( - ktype in self._wrapper_map, - 'Unkown key type: %s.' % str(ktype)) - try: - # return key if it exists in local database - return self._wrapper_map[ktype].get_key(address, private=private) - except KeyNotFound: - # we will only try to fetch a key from nickserver if fetch_remote - # is True and the key is not private. - if fetch_remote is False or private is True: - raise - self._fetch_keys_from_server(address) - return self._wrapper_map[ktype].get_key(address, private=False) - - def get_all_keys_in_local_db(self, private=False): - """ - Return all keys stored in local database. - - :return: A list with all keys in local db. - :rtype: list - """ - return map( - lambda doc: build_key_from_dict( - self._key_class_from_type(doc.content['type']), - doc.content['address'], - doc.content), - self._soledad.get_from_index( - TAGS_PRIVATE_INDEX, - KEYMANAGER_KEY_TAG, - '1' if private else '0')) - - def refresh_keys(self): - """ - Fetch keys from nickserver and update them locally. - """ - addresses = set(map( - lambda doc: doc.address, - self.get_all_keys_in_local_db(private=False))) - for address in addresses: - # do not attempt to refresh our own key - if address == self._address: - continue - self._fetch_keys_from_server(address) - - def gen_key(self, ktype): - """ - Generate a key of type C{ktype} bound to the user's address. - - :param ktype: The type of the key. - :type ktype: KeyType - - :return: The generated key. - :rtype: EncryptionKey - """ - return self._wrapper_map[ktype].gen_key(self._address) - - # - # Setters/getters - # - - def _get_session_id(self): - return self._session_id - - def _set_session_id(self, session_id): - self._session_id = session_id - - session_id = property( - _get_session_id, _set_session_id, doc='The session id.') - - def _get_ca_cert_path(self): - return self._ca_cert_path - - def _set_ca_cert_path(self, ca_cert_path): - self._ca_cert_path = ca_cert_path - - ca_cert_path = property( - _get_ca_cert_path, _set_ca_cert_path, - doc='The path to the CA certificate.') - - def _get_api_uri(self): - return self._api_uri - - def _set_api_uri(self, api_uri): - self._api_uri = api_uri - - api_uri = property( - _get_api_uri, _set_api_uri, doc='The webapp API URI.') - - def _get_api_version(self): - return self._api_version - - def _set_api_version(self, api_version): - self._api_version = api_version - - api_version = property( - _get_api_version, _set_api_version, doc='The webapp API version.') - - def _get_uid(self): - return self._uid - - def _set_uid(self, uid): - self._uid = uid - - uid = property( - _get_uid, _set_uid, doc='The uid of the user.') diff --git a/src/leap/common/keymanager/errors.py b/src/leap/common/keymanager/errors.py deleted file mode 100644 index 89949d2..0000000 --- a/src/leap/common/keymanager/errors.py +++ /dev/null @@ -1,86 +0,0 @@ -# -*- coding: utf-8 -*- -# errors.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -""" -Errors and exceptions used by the Key Manager. -""" - - -class KeyNotFound(Exception): - """ - Raised when key was no found on keyserver. - """ - pass - - -class KeyAlreadyExists(Exception): - """ - Raised when attempted to create a key that already exists. - """ - pass - - -class KeyAttributesDiffer(Exception): - """ - Raised when trying to delete a key but the stored key differs from the key - passed to the delete_key() method. - """ - pass - - -class NoPasswordGiven(Exception): - """ - Raised when trying to perform some action that needs a password without - providing one. - """ - pass - - -class InvalidSignature(Exception): - """ - Raised when signature could not be verified. - """ - pass - - -class EncryptionFailed(Exception): - """ - Raised upon failures of encryption. - """ - pass - - -class DecryptionFailed(Exception): - """ - Raised upon failures of decryption. - """ - pass - - -class EncryptionDecryptionFailed(Exception): - """ - Raised upon failures of encryption/decryption. - """ - pass - - -class SignFailed(Exception): - """ - Raised when failed to sign. - """ - pass diff --git a/src/leap/common/keymanager/gpg.py b/src/leap/common/keymanager/gpg.py deleted file mode 100644 index 15c1d9f..0000000 --- a/src/leap/common/keymanager/gpg.py +++ /dev/null @@ -1,397 +0,0 @@ -# -*- coding: utf-8 -*- -# gpgwrapper.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -""" -A GPG wrapper used to handle OpenPGP keys. - -This is a temporary class that will be superseded by the a revised version of -python-gnupg. -""" - - -import os -import gnupg -import re -from gnupg import ( - logger, - _is_sequence, - _make_binary_stream, -) - - -class ListPackets(): - """ - Handle status messages for --list-packets. - """ - - def __init__(self, gpg): - """ - Initialize the packet listing handling class. - - :param gpg: GPG object instance. - :type gpg: gnupg.GPG - """ - self.gpg = gpg - self.nodata = None - self.key = None - self.need_passphrase = None - self.need_passphrase_sym = None - self.userid_hint = None - - def handle_status(self, key, value): - """ - Handle one line of the --list-packets status message. - - :param key: The status message key. - :type key: str - :param value: The status message value. - :type value: str - """ - # TODO: write tests for handle_status - if key == 'NODATA': - self.nodata = True - if key == 'ENC_TO': - # This will only capture keys in our keyring. In the future we - # may want to include multiple unknown keys in this list. - self.key, _, _ = value.split() - if key == 'NEED_PASSPHRASE': - self.need_passphrase = True - if key == 'NEED_PASSPHRASE_SYM': - self.need_passphrase_sym = True - if key == 'USERID_HINT': - self.userid_hint = value.strip().split() - - -class GPGWrapper(gnupg.GPG): - """ - This is a temporary class for handling GPG requests, and should be - replaced by a more general class used throughout the project. - """ - - GNUPG_HOME = os.environ['HOME'] + "/.config/leap/gnupg" - GNUPG_BINARY = "/usr/bin/gpg" # this has to be changed based on OS - - def __init__(self, gpgbinary=GNUPG_BINARY, gnupghome=GNUPG_HOME, - verbose=False, use_agent=False, keyring=None, options=None): - """ - Initialize a GnuPG process wrapper. - - :param gpgbinary: Name for GnuPG binary executable. - :type gpgbinary: C{str} - :param gpghome: Full pathname to directory containing the public and - private keyrings. - :type gpghome: C{str} - :param keyring: Name of alternative keyring file to use. If specified, - the default keyring is not used. - :param verbose: Should some verbose info be output? - :type verbose: bool - :param use_agent: Should pass `--use-agent` to GPG binary? - :type use_agent: bool - :param keyring: Path for the keyring to use. - :type keyring: str - @options: A list of additional options to pass to the GPG binary. - :type options: list - - @raise: RuntimeError with explanation message if there is a problem - invoking gpg. - """ - gnupg.GPG.__init__(self, gnupghome=gnupghome, gpgbinary=gpgbinary, - verbose=verbose, use_agent=use_agent, - keyring=keyring, options=options) - self.result_map['list-packets'] = ListPackets - - def find_key_by_email(self, email, secret=False): - """ - Find user's key based on their email. - - :param email: Email address of key being searched for. - :type email: str - :param secret: Should we search for a secret key? - :type secret: bool - - :return: The fingerprint of the found key. - :rtype: str - """ - for key in self.list_keys(secret=secret): - for uid in key['uids']: - if re.search(email, uid): - return key - raise LookupError("GnuPG public key for email %s not found!" % email) - - def find_key_by_subkey(self, subkey, secret=False): - """ - Find user's key based on a subkey fingerprint. - - :param email: Subkey fingerprint of the key being searched for. - :type email: str - :param secret: Should we search for a secret key? - :type secret: bool - - :return: The fingerprint of the found key. - :rtype: str - """ - for key in self.list_keys(secret=secret): - for sub in key['subkeys']: - if sub[0] == subkey: - return key - raise LookupError( - "GnuPG public key for subkey %s not found!" % subkey) - - def find_key_by_keyid(self, keyid, secret=False): - """ - Find user's key based on the key ID. - - :param email: The key ID of the key being searched for. - :type email: str - :param secret: Should we search for a secret key? - :type secret: bool - - :return: The fingerprint of the found key. - :rtype: str - """ - for key in self.list_keys(secret=secret): - if keyid == key['keyid']: - return key - raise LookupError( - "GnuPG public key for keyid %s not found!" % keyid) - - def find_key_by_fingerprint(self, fingerprint, secret=False): - """ - Find user's key based on the key fingerprint. - - :param email: The fingerprint of the key being searched for. - :type email: str - :param secret: Should we search for a secret key? - :type secret: bool - - :return: The fingerprint of the found key. - :rtype: str - """ - for key in self.list_keys(secret=secret): - if fingerprint == key['fingerprint']: - return key - raise LookupError( - "GnuPG public key for fingerprint %s not found!" % fingerprint) - - def encrypt(self, data, recipient, sign=None, always_trust=True, - passphrase=None, symmetric=False): - """ - Encrypt data using GPG. - - :param data: The data to be encrypted. - :type data: str - :param recipient: The address of the public key to be used. - :type recipient: str - :param sign: Should the encrypted content be signed? - :type sign: bool - :param always_trust: Skip key validation and assume that used keys - are always fully trusted? - :type always_trust: bool - :param passphrase: The passphrase to be used if symmetric encryption - is desired. - :type passphrase: str - :param symmetric: Should we encrypt to a password? - :type symmetric: bool - - :return: An object with encrypted result in the `data` field. - :rtype: gnupg.Crypt - """ - # TODO: devise a way so we don't need to "always trust". - return gnupg.GPG.encrypt(self, data, recipient, sign=sign, - always_trust=always_trust, - passphrase=passphrase, - symmetric=symmetric, - cipher_algo='AES256') - - def decrypt(self, data, always_trust=True, passphrase=None): - """ - Decrypt data using GPG. - - :param data: The data to be decrypted. - :type data: str - :param always_trust: Skip key validation and assume that used keys - are always fully trusted? - :type always_trust: bool - :param passphrase: The passphrase to be used if symmetric encryption - is desired. - :type passphrase: str - - :return: An object with decrypted result in the `data` field. - :rtype: gnupg.Crypt - """ - # TODO: devise a way so we don't need to "always trust". - return gnupg.GPG.decrypt(self, data, always_trust=always_trust, - passphrase=passphrase) - - def send_keys(self, keyserver, *keyids): - """ - Send keys to a keyserver - - :param keyserver: The keyserver to send the keys to. - :type keyserver: str - :param keyids: The key ids to send. - :type keyids: list - - :return: A list of keys sent to server. - :rtype: gnupg.ListKeys - """ - # TODO: write tests for this. - # TODO: write a SendKeys class to handle status for this. - result = self.result_map['list'](self) - gnupg.logger.debug('send_keys: %r', keyids) - data = gnupg._make_binary_stream("", self.encoding) - args = ['--keyserver', keyserver, '--send-keys'] - args.extend(keyids) - self._handle_io(args, data, result, binary=True) - gnupg.logger.debug('send_keys result: %r', result.__dict__) - data.close() - return result - - def encrypt_file(self, file, recipients, sign=None, - always_trust=False, passphrase=None, - armor=True, output=None, symmetric=False, - cipher_algo=None): - """ - Encrypt the message read from the file-like object 'file'. - - :param file: The file to be encrypted. - :type data: file - :param recipient: The address of the public key to be used. - :type recipient: str - :param sign: Should the encrypted content be signed? - :type sign: bool - :param always_trust: Skip key validation and assume that used keys - are always fully trusted? - :type always_trust: bool - :param passphrase: The passphrase to be used if symmetric encryption - is desired. - :type passphrase: str - :param armor: Create ASCII armored output? - :type armor: bool - :param output: Path of file to write results in. - :type output: str - :param symmetric: Should we encrypt to a password? - :type symmetric: bool - :param cipher_algo: Algorithm to use. - :type cipher_algo: str - - :return: An object with encrypted result in the `data` field. - :rtype: gnupg.Crypt - """ - args = ['--encrypt'] - if symmetric: - args = ['--symmetric'] - if cipher_algo: - args.append('--cipher-algo %s' % cipher_algo) - else: - args = ['--encrypt'] - if not _is_sequence(recipients): - recipients = (recipients,) - for recipient in recipients: - args.append('--recipient "%s"' % recipient) - if armor: # create ascii-armored output - set to False for binary - args.append('--armor') - if output: # write the output to a file with the specified name - if os.path.exists(output): - os.remove(output) # to avoid overwrite confirmation message - args.append('--output "%s"' % output) - if sign: - args.append('--sign --default-key "%s"' % sign) - if always_trust: - args.append("--always-trust") - result = self.result_map['crypt'](self) - self._handle_io(args, file, result, passphrase=passphrase, binary=True) - logger.debug('encrypt result: %r', result.data) - return result - - def list_packets(self, data): - """ - List the sequence of packets. - - :param data: The data to extract packets from. - :type data: str - - :return: An object with packet info. - :rtype ListPackets - """ - args = ["--list-packets"] - result = self.result_map['list-packets'](self) - self._handle_io( - args, - _make_binary_stream(data, self.encoding), - result, - ) - return result - - def encrypted_to(self, data): - """ - Return the key to which data is encrypted to. - - :param data: The data to be examined. - :type data: str - - :return: The fingerprint of the key to which data is encrypted to. - :rtype: str - """ - # TODO: make this support multiple keys. - result = self.list_packets(data) - if not result.key: - raise LookupError( - "Content is not encrypted to a GnuPG key!") - try: - return self.find_key_by_keyid(result.key) - except: - return self.find_key_by_subkey(result.key) - - def is_encrypted_sym(self, data): - """ - Say whether some chunk of data is encrypted to a symmetric key. - - :param data: The data to be examined. - :type data: str - - :return: Whether data is encrypted to a symmetric key. - :rtype: bool - """ - result = self.list_packets(data) - return bool(result.need_passphrase_sym) - - def is_encrypted_asym(self, data): - """ - Say whether some chunk of data is encrypted to a private key. - - :param data: The data to be examined. - :type data: str - - :return: Whether data is encrypted to a private key. - :rtype: bool - """ - result = self.list_packets(data) - return bool(result.key) - - def is_encrypted(self, data): - """ - Say whether some chunk of data is encrypted to a key. - - :param data: The data to be examined. - :type data: str - - :return: Whether data is encrypted to a key. - :rtype: bool - """ - return self.is_encrypted_asym(data) or self.is_encrypted_sym(data) diff --git a/src/leap/common/keymanager/keys.py b/src/leap/common/keymanager/keys.py deleted file mode 100644 index a3c8537..0000000 --- a/src/leap/common/keymanager/keys.py +++ /dev/null @@ -1,284 +0,0 @@ -# -*- coding: utf-8 -*- -# keys.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -""" -Abstact key type and encryption scheme representations. -""" - - -try: - import simplejson as json -except ImportError: - import json # noqa -import re - - -from abc import ABCMeta, abstractmethod -from leap.common.check import leap_assert - - -# -# Dictionary keys used for storing cryptographic keys. -# - -KEY_ADDRESS_KEY = 'address' -KEY_TYPE_KEY = 'type' -KEY_ID_KEY = 'key_id' -KEY_FINGERPRINT_KEY = 'fingerprint' -KEY_DATA_KEY = 'key_data' -KEY_PRIVATE_KEY = 'private' -KEY_LENGTH_KEY = 'length' -KEY_EXPIRY_DATE_KEY = 'expiry_date' -KEY_FIRST_SEEN_AT_KEY = 'first_seen_at' -KEY_LAST_AUDITED_AT_KEY = 'last_audited_at' -KEY_VALIDATION_KEY = 'validation' -KEY_TAGS_KEY = 'tags' - - -# -# Key storage constants -# - -KEYMANAGER_KEY_TAG = 'keymanager-key' - - -# -# key indexing constants. -# - -TAGS_PRIVATE_INDEX = 'by-tags-private' -TAGS_ADDRESS_PRIVATE_INDEX = 'by-tags-address-private' -INDEXES = { - TAGS_PRIVATE_INDEX: [ - KEY_TAGS_KEY, - 'bool(%s)' % KEY_PRIVATE_KEY, - ], - TAGS_ADDRESS_PRIVATE_INDEX: [ - KEY_TAGS_KEY, - KEY_ADDRESS_KEY, - 'bool(%s)' % KEY_PRIVATE_KEY, - ] -} - - -# -# Key handling utilities -# - -def is_address(address): - """ - Return whether the given C{address} is in the form user@provider. - - :param address: The address to be tested. - :type address: str - :return: Whether C{address} is in the form user@provider. - :rtype: bool - """ - return bool(re.match('[\w.-]+@[\w.-]+', address)) - - -def build_key_from_dict(kClass, address, kdict): - """ - Build an C{kClass} key bound to C{address} based on info in C{kdict}. - - :param address: The address bound to the key. - :type address: str - :param kdict: Dictionary with key data. - :type kdict: dict - :return: An instance of the key. - :rtype: C{kClass} - """ - leap_assert( - address == kdict[KEY_ADDRESS_KEY], - 'Wrong address in key data.') - return kClass( - address, - key_id=kdict[KEY_ID_KEY], - fingerprint=kdict[KEY_FINGERPRINT_KEY], - key_data=kdict[KEY_DATA_KEY], - private=kdict[KEY_PRIVATE_KEY], - length=kdict[KEY_LENGTH_KEY], - expiry_date=kdict[KEY_EXPIRY_DATE_KEY], - first_seen_at=kdict[KEY_FIRST_SEEN_AT_KEY], - last_audited_at=kdict[KEY_LAST_AUDITED_AT_KEY], - validation=kdict[KEY_VALIDATION_KEY], # TODO: verify for validation. - ) - -# -# Abstraction for encryption keys -# - -class EncryptionKey(object): - """ - Abstract class for encryption keys. - - A key is "validated" if the nicknym agent has bound the user address to a - public key. Nicknym supports three different levels of key validation: - - * Level 3 - path trusted: A path of cryptographic signatures can be traced - from a trusted key to the key under evaluation. By default, only the - provider key from the user's provider is a "trusted key". - * level 2 - provider signed: The key has been signed by a provider key for - the same domain, but the provider key is not validated using a trust - path (i.e. it is only registered) - * level 1 - registered: The key has been encountered and saved, it has no - signatures (that are meaningful to the nicknym agent). - """ - - __metaclass__ = ABCMeta - - def __init__(self, address, key_id=None, fingerprint=None, - key_data=None, private=None, length=None, expiry_date=None, - validation=None, first_seen_at=None, last_audited_at=None): - self.address = address - self.key_id = key_id - self.fingerprint = fingerprint - self.key_data = key_data - self.private = private - self.length = length - self.expiry_date = expiry_date - self.validation = validation - self.first_seen_at = first_seen_at - self.last_audited_at = last_audited_at - - def get_json(self): - """ - Return a JSON string describing this key. - - :return: The JSON string describing this key. - :rtype: str - """ - return json.dumps({ - KEY_ADDRESS_KEY: self.address, - KEY_TYPE_KEY: str(self.__class__), - KEY_ID_KEY: self.key_id, - KEY_FINGERPRINT_KEY: self.fingerprint, - KEY_DATA_KEY: self.key_data, - KEY_PRIVATE_KEY: self.private, - KEY_LENGTH_KEY: self.length, - KEY_EXPIRY_DATE_KEY: self.expiry_date, - KEY_VALIDATION_KEY: self.validation, - KEY_FIRST_SEEN_AT_KEY: self.first_seen_at, - KEY_LAST_AUDITED_AT_KEY: self.last_audited_at, - KEY_TAGS_KEY: [KEYMANAGER_KEY_TAG], - }) - - def __repr__(self): - """ - Representation of this class - """ - return u"<%s 0x%s (%s - %s)>" % ( - self.__class__.__name__, - self.key_id, - self.address, - "priv" if self.private else "publ") - - -# -# Encryption schemes -# - -class EncryptionScheme(object): - """ - Abstract class for Encryption Schemes. - - A wrapper for a certain encryption schemes should know how to get and put - keys in local storage using Soledad, how to generate new keys and how to - find out about possibly encrypted content. - """ - - __metaclass__ = ABCMeta - - def __init__(self, soledad): - """ - Initialize this Encryption Scheme. - - :param soledad: A Soledad instance for local storage of keys. - :type soledad: leap.soledad.Soledad - """ - self._soledad = soledad - self._init_indexes() - - def _init_indexes(self): - """ - Initialize the database indexes. - """ - # Ask the database for currently existing indexes. - db_indexes = dict(self._soledad.list_indexes()) - # Loop through the indexes we expect to find. - for name, expression in INDEXES.items(): - if name not in db_indexes: - # The index does not yet exist. - self._soledad.create_index(name, *expression) - continue - if expression == db_indexes[name]: - # The index exists and is up to date. - continue - # The index exists but the definition is not what expected, so we - # delete it and add the proper index expression. - self._soledad.delete_index(name) - self._soledad.create_index(name, *expression) - - @abstractmethod - def get_key(self, address, private=False): - """ - Get key from local storage. - - :param address: The address bound to the key. - :type address: str - :param private: Look for a private key instead of a public one? - :type private: bool - - :return: The key bound to C{address}. - :rtype: EncryptionKey - @raise KeyNotFound: If the key was not found on local storage. - """ - pass - - @abstractmethod - def put_key(self, key): - """ - Put a key in local storage. - - :param key: The key to be stored. - :type key: EncryptionKey - """ - pass - - @abstractmethod - def gen_key(self, address): - """ - Generate a new key. - - :param address: The address bound to the key. - :type address: str - - :return: The key bound to C{address}. - :rtype: EncryptionKey - """ - pass - - @abstractmethod - def delete_key(self, key): - """ - Remove C{key} from storage. - - :param key: The key to be removed. - :type key: EncryptionKey - """ - pass diff --git a/src/leap/common/keymanager/openpgp.py b/src/leap/common/keymanager/openpgp.py deleted file mode 100644 index dd11157..0000000 --- a/src/leap/common/keymanager/openpgp.py +++ /dev/null @@ -1,636 +0,0 @@ -# -*- coding: utf-8 -*- -# openpgp.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -""" -Infrastructure for using OpenPGP keys in Key Manager. -""" -import logging -import os -import re -import shutil -import tempfile - -from leap.common.check import leap_assert, leap_assert_type -from leap.common.keymanager import errors -from leap.common.keymanager.keys import ( - EncryptionKey, - EncryptionScheme, - is_address, - build_key_from_dict, - KEYMANAGER_KEY_TAG, - TAGS_ADDRESS_PRIVATE_INDEX, -) -from leap.common.keymanager.gpg import GPGWrapper - -logger = logging.getLogger(__name__) - - -# -# gpg wrapper and decorator -# - -def temporary_gpgwrapper(keys=None): - """ - Returns a unitary gpg wrapper that implements context manager - protocol. - - :param key_data: ASCII armored key data. - :type key_data: str - - :return: a GPGWrapper instance - :rtype: GPGWrapper - """ - # TODO do here checks on key_data - return TempGPGWrapper(keys=keys) - - -def with_temporary_gpg(fun): - """ - Decorator to add a temporary gpg wrapper as context - to gpg related functions. - - Decorated functions are expected to return a function whose only - argument is a gpgwrapper instance. - """ - def wrapped(*args, **kwargs): - """ - We extract the arguments passed to the wrapped function, - run the function and do validations. - We expect that the positional arguments are `data`, - and an optional `key`. - All the rest of arguments should be passed as named arguments - to allow for a correct unpacking. - """ - if len(args) == 2: - keys = args[1] if isinstance(args[1], OpenPGPKey) else None - else: - keys = None - - # sign/verify keys passed as arguments - sign = kwargs.get('sign', None) - if sign: - keys = [keys, sign] - - verify = kwargs.get('verify', None) - if verify: - keys = [keys, verify] - - # is the wrapped function sign or verify? - fun_name = fun.__name__ - is_sign_function = True if fun_name == "sign" else False - is_verify_function = True if fun_name == "verify" else False - - result = None - - with temporary_gpgwrapper(keys) as gpg: - result = fun(*args, **kwargs)(gpg) - - # TODO: cleanup a little bit the - # validation. maybe delegate to other - # auxiliary functions for clarity. - - ok = getattr(result, 'ok', None) - - stderr = getattr(result, 'stderr', None) - if stderr: - logger.debug("%s" % (stderr,)) - - if ok is False: - raise errors.EncryptionDecryptionFailed( - 'Failed to encrypt/decrypt in %s: %s' % ( - fun.__name__, - stderr)) - - if verify is not None: - # A verify key has been passed - if result.valid is False or \ - verify.fingerprint != result.pubkey_fingerprint: - raise errors.InvalidSignature( - 'Failed to verify signature with key %s: %s' % - (verify.key_id, stderr)) - - if is_sign_function: - # Specific validation for sign function - privkey = gpg.list_keys(secret=True).pop() - rfprint = result.fingerprint - kfprint = privkey['fingerprint'] - if result.fingerprint is None: - raise errors.SignFailed( - 'Failed to sign with key %s: %s' % - (privkey['keyid'], stderr)) - leap_assert( - result.fingerprint == kfprint, - 'Signature and private key fingerprints mismatch: ' - '%s != %s' % - (rfprint, kfprint)) - - if is_verify_function: - # Specific validation for verify function - pubkey = gpg.list_keys().pop() - valid = result.valid - rfprint = result.fingerprint - kfprint = pubkey['fingerprint'] - if valid is False or rfprint != kfprint: - raise errors.InvalidSignature( - 'Failed to verify signature ' - 'with key %s.' % pubkey['keyid']) - result = result.valid - - # ok, enough checks. let's return data if available - if hasattr(result, 'data'): - result = result.data - return result - return wrapped - - -class TempGPGWrapper(object): - """ - A context manager returning a temporary GPG wrapper keyring, which - contains exactly zero or one pubkeys, and zero or one privkeys. - - Temporary unitary keyrings allow the to use GPG's facilities for exactly - one key. This function creates an empty temporary keyring and imports - C{keys} if it is not None. - """ - def __init__(self, keys=None): - """ - :param keys: OpenPGP key, or list of. - :type keys: OpenPGPKey or list of OpenPGPKeys - """ - self._gpg = None - if not keys: - keys = list() - if not isinstance(keys, list): - keys = [keys] - self._keys = keys - for key in filter(None, keys): - leap_assert_type(key, OpenPGPKey) - - def __enter__(self): - """ - Calls the unitary gpgwrapper initializer - - :return: A GPG wrapper with a unitary keyring. - :rtype: gnupg.GPG - """ - self._build_keyring() - return self._gpg - - def __exit__(self, exc_type, exc_value, traceback): - """ - Ensures the gpgwrapper is properly destroyed. - """ - # TODO handle exceptions and log here - self._destroy_keyring() - - def _build_keyring(self): - """ - Create an empty GPG keyring and import C{keys} into it. - - :param keys: List of keys to add to the keyring. - :type keys: list of OpenPGPKey - - :return: A GPG wrapper with a unitary keyring. - :rtype: gnupg.GPG - """ - privkeys = [key for key in self._keys if key and key.private is True] - publkeys = [key for key in self._keys if key and key.private is False] - # here we filter out public keys that have a correspondent - # private key in the list because the private key_data by - # itself is enough to also have the public key in the keyring, - # and we want to count the keys afterwards. - - privaddrs = map(lambda privkey: privkey.address, privkeys) - publkeys = filter( - lambda pubkey: pubkey.address not in privaddrs, publkeys) - - listkeys = lambda: self._gpg.list_keys() - listsecretkeys = lambda: self._gpg.list_keys(secret=True) - - self._gpg = GPGWrapper(gnupghome=tempfile.mkdtemp()) - leap_assert(len(listkeys()) is 0, 'Keyring not empty.') - - # import keys into the keyring: - # concatenating ascii-armored keys, which is correctly - # understood by the GPGWrapper. - - self._gpg.import_keys("".join( - [x.key_data for x in publkeys + privkeys])) - - # assert the number of keys in the keyring - leap_assert( - len(listkeys()) == len(publkeys) + len(privkeys), - 'Wrong number of public keys in keyring: %d, should be %d)' % - (len(listkeys()), len(publkeys) + len(privkeys))) - leap_assert( - len(listsecretkeys()) == len(privkeys), - 'Wrong number of private keys in keyring: %d, should be %d)' % - (len(listsecretkeys()), len(privkeys))) - - def _destroy_keyring(self): - """ - Securely erase a unitary keyring. - """ - # TODO: implement some kind of wiping of data or a more - # secure way that - # does not write to disk. - - try: - for secret in [True, False]: - for key in self._gpg.list_keys(secret=secret): - self._gpg.delete_keys( - key['fingerprint'], - secret=secret) - leap_assert(len(self._gpg.list_keys()) is 0, 'Keyring not empty!') - - except: - raise - - finally: - leap_assert(self._gpg.gnupghome != os.path.expanduser('~/.gnupg'), - "watch out! Tried to remove default gnupg home!") - shutil.rmtree(self._gpg.gnupghome) - - -# -# API functions -# - -@with_temporary_gpg -def encrypt_asym(data, key, passphrase=None, sign=None): - """ - Encrypt C{data} using public @{key} and sign with C{sign} key. - - :param data: The data to be encrypted. - :type data: str - :param pubkey: The key used to encrypt. - :type pubkey: OpenPGPKey - :param sign: The key used for signing. - :type sign: OpenPGPKey - - :return: The encrypted data. - :rtype: str - """ - leap_assert_type(key, OpenPGPKey) - leap_assert(key.private is False, 'Key is not public.') - if sign is not None: - leap_assert_type(sign, OpenPGPKey) - leap_assert(sign.private is True) - - # Here we cannot assert for correctness of sig because the sig is in - # the ciphertext. - # result.ok - (bool) indicates if the operation succeeded - # result.data - (bool) contains the result of the operation - - return lambda gpg: gpg.encrypt( - data, key.fingerprint, - sign=sign.key_id if sign else None, - passphrase=passphrase, symmetric=False) - - -@with_temporary_gpg -def decrypt_asym(data, key, passphrase=None, verify=None): - """ - Decrypt C{data} using private @{key} and verify with C{verify} key. - - :param data: The data to be decrypted. - :type data: str - :param privkey: The key used to decrypt. - :type privkey: OpenPGPKey - :param verify: The key used to verify a signature. - :type verify: OpenPGPKey - - :return: The decrypted data. - :rtype: str - - @raise InvalidSignature: Raised if unable to verify the signature with - C{verify} key. - """ - leap_assert(key.private is True, 'Key is not private.') - if verify is not None: - leap_assert_type(verify, OpenPGPKey) - leap_assert(verify.private is False) - - return lambda gpg: gpg.decrypt( - data, passphrase=passphrase) - - -@with_temporary_gpg -def is_encrypted(data): - """ - Return whether C{data} was encrypted using OpenPGP. - - :param data: The data we want to know about. - :type data: str - - :return: Whether C{data} was encrypted using this wrapper. - :rtype: bool - """ - return lambda gpg: gpg.is_encrypted(data) - - -@with_temporary_gpg -def is_encrypted_asym(data): - """ - Return whether C{data} was asymmetrically encrypted using OpenPGP. - - :param data: The data we want to know about. - :type data: str - - :return: Whether C{data} was encrypted using this wrapper. - :rtype: bool - """ - return lambda gpg: gpg.is_encrypted_asym(data) - - -@with_temporary_gpg -def sign(data, privkey): - """ - Sign C{data} with C{privkey}. - - :param data: The data to be signed. - :type data: str - - :param privkey: The private key to be used to sign. - :type privkey: OpenPGPKey - - :return: The ascii-armored signed data. - :rtype: str - """ - leap_assert_type(privkey, OpenPGPKey) - leap_assert(privkey.private is True) - - # result.fingerprint - contains the fingerprint of the key used to - # sign. - return lambda gpg: gpg.sign(data, keyid=privkey.key_id) - - -@with_temporary_gpg -def verify(data, key): - """ - Verify signed C{data} with C{pubkey}. - - :param data: The data to be verified. - :type data: str - - :param pubkey: The public key to be used on verification. - :type pubkey: OpenPGPKey - - :return: The ascii-armored signed data. - :rtype: str - """ - leap_assert_type(key, OpenPGPKey) - leap_assert(key.private is False) - - return lambda gpg: gpg.verify(data) - - -# -# Helper functions -# - - -def _build_key_from_gpg(address, key, key_data): - """ - Build an OpenPGPKey for C{address} based on C{key} from - local gpg storage. - - ASCII armored GPG key data has to be queried independently in this - wrapper, so we receive it in C{key_data}. - - :param address: The address bound to the key. - :type address: str - :param key: Key obtained from GPG storage. - :type key: dict - :param key_data: Key data obtained from GPG storage. - :type key_data: str - :return: An instance of the key. - :rtype: OpenPGPKey - """ - return OpenPGPKey( - address, - key_id=key['keyid'], - fingerprint=key['fingerprint'], - key_data=key_data, - private=True if key['type'] == 'sec' else False, - length=key['length'], - expiry_date=key['expires'], - validation=None, # TODO: verify for validation. - ) - - -# -# The OpenPGP wrapper -# - -class OpenPGPKey(EncryptionKey): - """ - Base class for OpenPGP keys. - """ - - -class OpenPGPScheme(EncryptionScheme): - """ - A wrapper for OpenPGP keys. - """ - - def __init__(self, soledad): - """ - Initialize the OpenPGP wrapper. - - :param soledad: A Soledad instance for key storage. - :type soledad: leap.soledad.Soledad - """ - EncryptionScheme.__init__(self, soledad) - - def gen_key(self, address): - """ - Generate an OpenPGP keypair bound to C{address}. - - :param address: The address bound to the key. - :type address: str - :return: The key bound to C{address}. - :rtype: OpenPGPKey - @raise KeyAlreadyExists: If key already exists in local database. - """ - # make sure the key does not already exist - leap_assert(is_address(address), 'Not an user address: %s' % address) - try: - self.get_key(address) - raise errors.KeyAlreadyExists(address) - except errors.KeyNotFound: - pass - - def _gen_key(gpg): - params = gpg.gen_key_input( - key_type='RSA', - key_length=4096, - name_real=address, - name_email=address, - name_comment='Generated by LEAP Key Manager.') - gpg.gen_key(params) - pubkeys = gpg.list_keys() - # assert for new key characteristics - leap_assert( - len(pubkeys) is 1, # a unitary keyring! - 'Keyring has wrong number of keys: %d.' % len(pubkeys)) - key = gpg.list_keys(secret=True).pop() - leap_assert( - len(key['uids']) is 1, # with just one uid! - 'Wrong number of uids for key: %d.' % len(key['uids'])) - leap_assert( - re.match('.*<%s>$' % address, key['uids'][0]) is not None, - 'Key not correctly bound to address.') - # insert both public and private keys in storage - for secret in [True, False]: - key = gpg.list_keys(secret=secret).pop() - openpgp_key = _build_key_from_gpg( - address, key, - gpg.export_keys(key['fingerprint'], secret=secret)) - self.put_key(openpgp_key) - - with temporary_gpgwrapper() as gpg: - # TODO: inspect result, or use decorator - _gen_key(gpg) - - return self.get_key(address, private=True) - - def get_key(self, address, private=False): - """ - Get key bound to C{address} from local storage. - - :param address: The address bound to the key. - :type address: str - :param private: Look for a private key instead of a public one? - :type private: bool - - :return: The key bound to C{address}. - :rtype: OpenPGPKey - @raise KeyNotFound: If the key was not found on local storage. - """ - leap_assert(is_address(address), 'Not an user address: %s' % address) - doc = self._get_key_doc(address, private) - if doc is None: - raise errors.KeyNotFound(address) - return build_key_from_dict(OpenPGPKey, address, doc.content) - - def put_ascii_key(self, key_data): - """ - Put key contained in ascii-armored C{key_data} in local storage. - - :param key_data: The key data to be stored. - :type key_data: str - """ - leap_assert_type(key_data, str) - # TODO: add more checks for correct key data. - leap_assert(key_data is not None, 'Data does not represent a key.') - - def _put_ascii_key(gpg): - gpg.import_keys(key_data) - privkey = None - pubkey = None - - try: - privkey = gpg.list_keys(secret=True).pop() - except IndexError: - pass - pubkey = gpg.list_keys(secret=False).pop() # unitary keyring - # extract adress from first uid on key - match = re.match('.*<([\w.-]+@[\w.-]+)>.*', pubkey['uids'].pop()) - leap_assert(match is not None, 'No user address in key data.') - address = match.group(1) - if privkey is not None: - match = re.match( - '.*<([\w.-]+@[\w.-]+)>.*', privkey['uids'].pop()) - leap_assert(match is not None, 'No user address in key data.') - privaddress = match.group(1) - leap_assert( - address == privaddress, - 'Addresses in pub and priv key differ.') - leap_assert( - pubkey['fingerprint'] == privkey['fingerprint'], - 'Fingerprints for pub and priv key differ.') - # insert private key in storage - openpgp_privkey = _build_key_from_gpg( - address, privkey, - gpg.export_keys(privkey['fingerprint'], secret=True)) - self.put_key(openpgp_privkey) - # insert public key in storage - openpgp_pubkey = _build_key_from_gpg( - address, pubkey, - gpg.export_keys(pubkey['fingerprint'], secret=False)) - self.put_key(openpgp_pubkey) - - with temporary_gpgwrapper() as gpg: - # TODO: inspect result, or use decorator - _put_ascii_key(gpg) - - def put_key(self, key): - """ - Put C{key} in local storage. - - :param key: The key to be stored. - :type key: OpenPGPKey - """ - doc = self._get_key_doc(key.address, private=key.private) - if doc is None: - self._soledad.create_doc_from_json(key.get_json()) - else: - doc.set_json(key.get_json()) - self._soledad.put_doc(doc) - - def _get_key_doc(self, address, private=False): - """ - Get the document with a key (public, by default) bound to C{address}. - - If C{private} is True, looks for a private key instead of a public. - - :param address: The address bound to the key. - :type address: str - :param private: Whether to look for a private key. - :type private: bool - :return: The document with the key or None if it does not exist. - :rtype: leap.soledad.backends.leap_backend.LeapDocument - """ - doclist = self._soledad.get_from_index( - TAGS_ADDRESS_PRIVATE_INDEX, - KEYMANAGER_KEY_TAG, - address, - '1' if private else '0') - if len(doclist) is 0: - return None - leap_assert( - len(doclist) is 1, - 'Found more than one %s key for address!' % - 'private' if private else 'public') - return doclist.pop() - - def delete_key(self, key): - """ - Remove C{key} from storage. - - :param key: The key to be removed. - :type key: EncryptionKey - """ - leap_assert(key.__class__ is OpenPGPKey, 'Wrong key type.') - stored_key = self.get_key(key.address, private=key.private) - if stored_key is None: - raise errors.KeyNotFound(key) - if stored_key.__dict__ != key.__dict__: - raise errors.KeyAttributesDiffer(key) - doc = self._get_key_doc(key.address, key.private) - self._soledad.delete_doc(doc) diff --git a/src/leap/common/tests/test_keymanager.py b/src/leap/common/tests/test_keymanager.py deleted file mode 100644 index 73611b6..0000000 --- a/src/leap/common/tests/test_keymanager.py +++ /dev/null @@ -1,686 +0,0 @@ -## -*- coding: utf-8 -*- -# test_keymanager.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -""" -Tests for the Key Manager. -""" - - -from mock import Mock -try: - import simplejson as json -except ImportError: - import json # noqa - - -from leap.common.testing.basetest import BaseLeapTest -from leap.soledad import Soledad -#from leap.soledad.crypto import SoledadCrypto - -from leap.common.keymanager import ( - KeyManager, - openpgp, - KeyNotFound, - NoPasswordGiven, - #TAGS_INDEX, - #TAGS_AND_PRIVATE_INDEX, -) -from leap.common.keymanager.openpgp import OpenPGPKey -from leap.common.keymanager.keys import ( - is_address, - build_key_from_dict, -) -from leap.common.keymanager import errors - - -ADDRESS = 'leap@leap.se' -ADDRESS_2 = 'anotheruser@leap.se' - - -class KeyManagerUtilTestCase(BaseLeapTest): - - def setUp(self): - pass - - def tearDown(self): - pass - - def test_is_address(self): - self.assertTrue( - is_address('user@leap.se'), - 'Incorrect address detection.') - self.assertFalse( - is_address('userleap.se'), - 'Incorrect address detection.') - self.assertFalse( - is_address('user@'), - 'Incorrect address detection.') - self.assertFalse( - is_address('@leap.se'), - 'Incorrect address detection.') - - def test_build_key_from_dict(self): - kdict = { - 'address': ADDRESS, - 'key_id': 'key_id', - 'fingerprint': 'fingerprint', - 'key_data': 'key_data', - 'private': 'private', - 'length': 'length', - 'expiry_date': 'expiry_date', - 'first_seen_at': 'first_seen_at', - 'last_audited_at': 'last_audited_at', - 'validation': 'validation', - } - key = build_key_from_dict(OpenPGPKey, ADDRESS, kdict) - self.assertEqual( - kdict['address'], key.address, - 'Wrong data in key.') - self.assertEqual( - kdict['key_id'], key.key_id, - 'Wrong data in key.') - self.assertEqual( - kdict['fingerprint'], key.fingerprint, - 'Wrong data in key.') - self.assertEqual( - kdict['key_data'], key.key_data, - 'Wrong data in key.') - self.assertEqual( - kdict['private'], key.private, - 'Wrong data in key.') - self.assertEqual( - kdict['length'], key.length, - 'Wrong data in key.') - self.assertEqual( - kdict['expiry_date'], key.expiry_date, - 'Wrong data in key.') - self.assertEqual( - kdict['first_seen_at'], key.first_seen_at, - 'Wrong data in key.') - self.assertEqual( - kdict['last_audited_at'], key.last_audited_at, - 'Wrong data in key.') - self.assertEqual( - kdict['validation'], key.validation, - 'Wrong data in key.') - - -class KeyManagerWithSoledadTestCase(BaseLeapTest): - - def setUp(self): - # mock key fetching and storing so Soledad doesn't fail when trying to - # reach the server. - Soledad._get_secrets_from_shared_db = Mock(return_value=None) - Soledad._put_secrets_in_shared_db = Mock(return_value=None) - - self._soledad = Soledad( - "leap@leap.se", - "123456", - self.tempdir+"/secret.gpg", - self.tempdir+"/soledad.u1db", - '', - None, - auth_token=None, - ) - - def tearDown(self): - km = self._key_manager() - for key in km.get_all_keys_in_local_db(): - km._wrapper_map[key.__class__].delete_key(key) - for key in km.get_all_keys_in_local_db(private=True): - km._wrapper_map[key.__class__].delete_key(key) - - def _key_manager(self, user=ADDRESS, url=''): - return KeyManager(user, url, self._soledad) - - -class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): - - def _test_openpgp_gen_key(self): - pgp = openpgp.OpenPGPScheme(self._soledad) - self.assertRaises(KeyNotFound, pgp.get_key, 'user@leap.se') - key = pgp.gen_key('user@leap.se') - self.assertIsInstance(key, openpgp.OpenPGPKey) - self.assertEqual( - 'user@leap.se', key.address, 'Wrong address bound to key.') - self.assertEqual( - '4096', key.length, 'Wrong key length.') - - def test_openpgp_put_delete_key(self): - pgp = openpgp.OpenPGPScheme(self._soledad) - self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - pgp.put_ascii_key(PUBLIC_KEY) - key = pgp.get_key(ADDRESS, private=False) - pgp.delete_key(key) - self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - - def test_openpgp_put_ascii_key(self): - pgp = openpgp.OpenPGPScheme(self._soledad) - self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - pgp.put_ascii_key(PUBLIC_KEY) - key = pgp.get_key(ADDRESS, private=False) - self.assertIsInstance(key, openpgp.OpenPGPKey) - self.assertEqual( - ADDRESS, key.address, 'Wrong address bound to key.') - self.assertEqual( - '4096', key.length, 'Wrong key length.') - pgp.delete_key(key) - self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - - def test_get_public_key(self): - pgp = openpgp.OpenPGPScheme(self._soledad) - self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - pgp.put_ascii_key(PUBLIC_KEY) - self.assertRaises( - KeyNotFound, pgp.get_key, ADDRESS, private=True) - key = pgp.get_key(ADDRESS, private=False) - self.assertEqual(ADDRESS, key.address) - self.assertFalse(key.private) - self.assertEqual(KEY_FINGERPRINT, key.fingerprint) - pgp.delete_key(key) - self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - - def test_openpgp_encrypt_decrypt_asym(self): - # encrypt - pgp = openpgp.OpenPGPScheme(self._soledad) - pgp.put_ascii_key(PUBLIC_KEY) - pubkey = pgp.get_key(ADDRESS, private=False) - cyphertext = openpgp.encrypt_asym('data', pubkey) - # assert - self.assertTrue(cyphertext is not None) - self.assertTrue(cyphertext != '') - self.assertTrue(cyphertext != 'data') - self.assertTrue(openpgp.is_encrypted_asym(cyphertext)) - self.assertTrue(openpgp.is_encrypted(cyphertext)) - # decrypt - self.assertRaises( - KeyNotFound, pgp.get_key, ADDRESS, private=True) - pgp.put_ascii_key(PRIVATE_KEY) - privkey = pgp.get_key(ADDRESS, private=True) - plaintext = openpgp.decrypt_asym(cyphertext, privkey) - pgp.delete_key(pubkey) - pgp.delete_key(privkey) - self.assertRaises( - KeyNotFound, pgp.get_key, ADDRESS, private=False) - self.assertRaises( - KeyNotFound, pgp.get_key, ADDRESS, private=True) - - def test_verify_with_private_raises(self): - pgp = openpgp.OpenPGPScheme(self._soledad) - pgp.put_ascii_key(PRIVATE_KEY) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - signed = openpgp.sign(data, privkey) - self.assertRaises( - AssertionError, - openpgp.verify, signed, privkey) - - def test_sign_with_public_raises(self): - pgp = openpgp.OpenPGPScheme(self._soledad) - pgp.put_ascii_key(PUBLIC_KEY) - data = 'data' - pubkey = pgp.get_key(ADDRESS, private=False) - self.assertRaises( - AssertionError, - openpgp.sign, data, pubkey) - - def test_verify_with_wrong_key_raises(self): - pgp = openpgp.OpenPGPScheme(self._soledad) - pgp.put_ascii_key(PRIVATE_KEY) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - signed = openpgp.sign(data, privkey) - pgp.put_ascii_key(PUBLIC_KEY_2) - wrongkey = pgp.get_key(ADDRESS_2) - self.assertRaises( - errors.InvalidSignature, - openpgp.verify, signed, wrongkey) - - def test_encrypt_asym_sign_with_public_raises(self): - pgp = openpgp.OpenPGPScheme(self._soledad) - pgp.put_ascii_key(PRIVATE_KEY) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - pubkey = pgp.get_key(ADDRESS, private=False) - self.assertRaises( - AssertionError, - openpgp.encrypt_asym, data, privkey, sign=pubkey) - - def test_decrypt_asym_verify_with_private_raises(self): - pgp = openpgp.OpenPGPScheme(self._soledad) - pgp.put_ascii_key(PRIVATE_KEY) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - pubkey = pgp.get_key(ADDRESS, private=False) - encrypted_and_signed = openpgp.encrypt_asym( - data, pubkey, sign=privkey) - self.assertRaises( - AssertionError, - openpgp.decrypt_asym, - encrypted_and_signed, privkey, verify=privkey) - - def test_decrypt_asym_verify_with_wrong_key_raises(self): - pgp = openpgp.OpenPGPScheme(self._soledad) - pgp.put_ascii_key(PRIVATE_KEY) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - pubkey = pgp.get_key(ADDRESS, private=False) - encrypted_and_signed = openpgp.encrypt_asym(data, pubkey, sign=privkey) - pgp.put_ascii_key(PUBLIC_KEY_2) - wrongkey = pgp.get_key(ADDRESS_2) - self.assertRaises( - errors.InvalidSignature, - openpgp.verify, encrypted_and_signed, wrongkey) - - def test_sign_verify(self): - pgp = openpgp.OpenPGPScheme(self._soledad) - pgp.put_ascii_key(PRIVATE_KEY) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - signed = openpgp.sign(data, privkey) - pubkey = pgp.get_key(ADDRESS, private=False) - self.assertTrue(openpgp.verify(signed, pubkey)) - - def test_encrypt_asym_sign_decrypt_verify(self): - pgp = openpgp.OpenPGPScheme(self._soledad) - pgp.put_ascii_key(PRIVATE_KEY) - pubkey = pgp.get_key(ADDRESS, private=False) - privkey = pgp.get_key(ADDRESS, private=True) - pgp.put_ascii_key(PRIVATE_KEY_2) - pubkey2 = pgp.get_key(ADDRESS_2, private=False) - privkey2 = pgp.get_key(ADDRESS_2, private=True) - data = 'data' - encrypted_and_signed = openpgp.encrypt_asym( - data, pubkey2, sign=privkey) - res = openpgp.decrypt_asym( - encrypted_and_signed, privkey2, verify=pubkey) - self.assertTrue(data, res) - - -class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): - - def test_get_all_keys_in_db(self): - km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) - # get public keys - keys = km.get_all_keys_in_local_db(False) - self.assertEqual(len(keys), 1, 'Wrong number of keys') - self.assertEqual(ADDRESS, keys[0].address) - self.assertFalse(keys[0].private) - # get private keys - keys = km.get_all_keys_in_local_db(True) - self.assertEqual(len(keys), 1, 'Wrong number of keys') - self.assertEqual(ADDRESS, keys[0].address) - self.assertTrue(keys[0].private) - - def test_get_public_key(self): - km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) - # get the key - key = km.get_key(ADDRESS, OpenPGPKey, private=False, - fetch_remote=False) - self.assertTrue(key is not None) - self.assertEqual(key.address, ADDRESS) - self.assertEqual( - key.fingerprint.lower(), KEY_FINGERPRINT.lower()) - self.assertFalse(key.private) - - def test_get_private_key(self): - km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) - # get the key - key = km.get_key(ADDRESS, OpenPGPKey, private=True, - fetch_remote=False) - self.assertTrue(key is not None) - self.assertEqual(key.address, ADDRESS) - self.assertEqual( - key.fingerprint.lower(), KEY_FINGERPRINT.lower()) - self.assertTrue(key.private) - - def test_send_key_raises_key_not_found(self): - km = self._key_manager() - self.assertRaises( - KeyNotFound, - km.send_key, OpenPGPKey) - - def test_send_key(self): - """ - Test that request is well formed when sending keys to server. - """ - km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY) - km._fetcher.put = Mock() - # the following data will be used on the send - km.ca_cert_path = 'capath' - km.session_id = 'sessionid' - km.uid = 'myuid' - km.api_uri = 'apiuri' - km.api_version = 'apiver' - km.send_key(OpenPGPKey) - # setup expected args - data = { - km.PUBKEY_KEY: km.get_key(km._address, OpenPGPKey).key_data, - } - url = '%s/%s/users/%s.json' % ('apiuri', 'apiver', 'myuid') - km._fetcher.put.assert_called_once_with( - url, data=data, verify='capath', - cookies={'_session_id': 'sessionid'}, - ) - - def test__fetch_keys_from_server(self): - """ - Test that the request is well formed when fetching keys from server. - """ - km = self._key_manager(url='http://nickserver.domain') - - class Response(object): - status_code = 200 - headers = {'content-type': 'application/json'} - - def json(self): - return {'address': ADDRESS_2, 'openpgp': PUBLIC_KEY_2} - - def raise_for_status(self): - pass - - # mock the fetcher so it returns the key for ADDRESS_2 - km._fetcher.get = Mock( - return_value=Response()) - km.ca_cert_path = 'cacertpath' - # do the fetch - km._fetch_keys_from_server(ADDRESS_2) - # and verify the call - km._fetcher.get.assert_called_once_with( - 'http://nickserver.domain', - data={'address': ADDRESS_2}, - verify='cacertpath', - ) - - def test_refresh_keys_does_not_refresh_own_key(self): - """ - Test that refreshing keys will not attempt to refresh our own key. - """ - km = self._key_manager() - # we add 2 keys but we expect it to only refresh the second one. - km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY) - km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY_2) - # mock the key fetching - km._fetch_keys_from_server = Mock(return_value=[]) - km.ca_cert_path = '' # some bogus path so the km does not complain. - # do the refreshing - km.refresh_keys() - km._fetch_keys_from_server.assert_called_once_with( - ADDRESS_2 - ) - - def test_get_key_fetches_from_server(self): - """ - Test that getting a key successfuly fetches from server. - """ - km = self._key_manager(url='http://nickserver.domain') - - class Response(object): - status_code = 200 - headers = {'content-type': 'application/json'} - - def json(self): - return {'address': ADDRESS_2, 'openpgp': PUBLIC_KEY_2} - - def raise_for_status(self): - pass - - # mock the fetcher so it returns the key for ADDRESS_2 - km._fetcher.get = Mock(return_value=Response()) - km.ca_cert_path = 'cacertpath' - # try to key get without fetching from server - self.assertRaises( - KeyNotFound, km.get_key, ADDRESS_2, OpenPGPKey, - fetch_remote=False - ) - # try to get key fetching from server. - key = km.get_key(ADDRESS_2, OpenPGPKey) - self.assertIsInstance(key, OpenPGPKey) - self.assertEqual(ADDRESS_2, key.address) - - -# Key material for testing - -# key 24D18DDF: public key "Leap Test Key " -KEY_FINGERPRINT = "E36E738D69173C13D709E44F2F455E2824D18DDF" -PUBLIC_KEY = """ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1.4.10 (GNU/Linux) - -mQINBFC9+dkBEADNRfwV23TWEoGc/x0wWH1P7PlXt8MnC2Z1kKaKKmfnglVrpOiz -iLWoiU58sfZ0L5vHkzXHXCBf6Eiy/EtUIvdiWAn+yASJ1mk5jZTBKO/WMAHD8wTO -zpMsFmWyg3xc4DkmFa9KQ5EVU0o/nqPeyQxNMQN7px5pPwrJtJFmPxnxm+aDkPYx -irDmz/4DeDNqXliazGJKw7efqBdlwTHkl9Akw2gwy178pmsKwHHEMOBOFFvX61AT -huKqHYmlCGSliwbrJppTG7jc1/ls3itrK+CWTg4txREkSpEVmfcASvw/ZqLbjgfs -d/INMwXnR9U81O8+7LT6yw/ca4ppcFoJD7/XJbkRiML6+bJ4Dakiy6i727BzV17g -wI1zqNvm5rAhtALKfACha6YO43aJzairO4II1wxVHvRDHZn2IuKDDephQ3Ii7/vb -hUOf6XCSmchkAcpKXUOvbxm1yfB1LRa64mMc2RcZxf4mW7KQkulBsdV5QG2276lv -U2UUy2IutXcGP5nXC+f6sJJGJeEToKJ57yiO/VWJFjKN8SvP+7AYsQSqINUuEf6H -T5gCPCraGMkTUTPXrREvu7NOohU78q6zZNaL3GW8ai7eSeANSuQ8Vzffx7Wd8Y7i -Pw9sYj0SMFs1UgjbuL6pO5ueHh+qyumbtAq2K0Bci0kqOcU4E9fNtdiovQARAQAB -tBxMZWFwIFRlc3QgS2V5IDxsZWFwQGxlYXAuc2U+iQI3BBMBCAAhBQJQvfnZAhsD -BQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAAAoJEC9FXigk0Y3fT7EQAKH3IuRniOpb -T/DDIgwwjz3oxB/W0DDMyPXowlhSOuM0rgGfntBpBb3boezEXwL86NPQxNGGruF5 -hkmecSiuPSvOmQlqlS95NGQp6hNG0YaKColh+Q5NTspFXCAkFch9oqUje0LdxfSP -QfV9UpeEvGyPmk1I9EJV/YDmZ4+Djge1d7qhVZInz4Rx1NrSyF/Tc2EC0VpjQFsU -Y9Kb2YBBR7ivG6DBc8ty0jJXi7B4WjkFcUEJviQpMF2dCLdonCehYs1PqsN1N7j+ -eFjQd+hqVMJgYuSGKjvuAEfClM6MQw7+FmFwMyLgK/Ew/DttHEDCri77SPSkOGSI -txCzhTg6798f6mJr7WcXmHX1w1Vcib5FfZ8vTDFVhz/XgAgArdhPo9V6/1dgSSiB -KPQ/spsco6u5imdOhckERE0lnAYvVT6KE81TKuhF/b23u7x+Wdew6kK0EQhYA7wy -7LmlaNXc7rMBQJ9Z60CJ4JDtatBWZ0kNrt2VfdDHVdqBTOpl0CraNUjWE5YMDasr -K2dF5IX8D3uuYtpZnxqg0KzyLg0tzL0tvOL1C2iudgZUISZNPKbS0z0v+afuAAnx -2pTC3uezbh2Jt8SWTLhll4i0P4Ps5kZ6HQUO56O+/Z1cWovX+mQekYFmERySDR9n -3k1uAwLilJmRmepGmvYbB8HloV8HqwgguQINBFC9+dkBEAC0I/xn1uborMgDvBtf -H0sEhwnXBC849/32zic6udB6/3Efk9nzbSpL3FSOuXITZsZgCHPkKarnoQ2ztMcS -sh1ke1C5gQGms75UVmM/nS+2YI4vY8OX/GC/on2vUyncqdH+bR6xH5hx4NbWpfTs -iQHmz5C6zzS/kuabGdZyKRaZHt23WQ7JX/4zpjqbC99DjHcP9BSk7tJ8wI4bkMYD -uFVQdT9O6HwyKGYwUU4sAQRAj7XCTGvVbT0dpgJwH4RmrEtJoHAx4Whg8mJ710E0 -GCmzf2jqkNuOw76ivgk27Kge+Hw00jmJjQhHY0yVbiaoJwcRrPKzaSjEVNgrpgP3 -lXPRGQArgESsIOTeVVHQ8fhK2YtTeCY9rIiO+L0OX2xo9HK7hfHZZWL6rqymXdyS -fhzh/f6IPyHFWnvj7Brl7DR8heMikygcJqv+ed2yx7iLyCUJ10g12I48+aEj1aLe -dP7lna32iY8/Z0SHQLNH6PXO9SlPcq2aFUgKqE75A/0FMk7CunzU1OWr2ZtTLNO1 -WT/13LfOhhuEq9jTyTosn0WxBjJKq18lnhzCXlaw6EAtbA7CUwsD3CTPR56aAXFK -3I7KXOVAqggrvMe5Tpdg5drfYpI8hZovL5aAgb+7Y5ta10TcJdUhS5K3kFAWe/td -U0cmWUMDP1UMSQ5Jg6JIQVWhSwARAQABiQIfBBgBCAAJBQJQvfnZAhsMAAoJEC9F -Xigk0Y3fRwsP/i0ElYCyxeLpWJTwo1iCLkMKz2yX1lFVa9nT1BVTPOQwr/IAc5OX -NdtbJ14fUsKL5pWgW8OmrXtwZm1y4euI1RPWWubG01ouzwnGzv26UcuHeqC5orZj -cOnKtL40y8VGMm8LoicVkRJH8blPORCnaLjdOtmA3rx/v2EXrJpSa3AhOy0ZSRXk -ZSrK68AVNwamHRoBSYyo0AtaXnkPX4+tmO8X8BPfj125IljubvwZPIW9VWR9UqCE -VPfDR1XKegVb6VStIywF7kmrknM1C5qUY28rdZYWgKorw01hBGV4jTW0cqde3N51 -XT1jnIAa+NoXUM9uQoGYMiwrL7vNsLlyyiW5ayDyV92H/rIuiqhFgbJsHTlsm7I8 -oGheR784BagAA1NIKD1qEO9T6Kz9lzlDaeWS5AUKeXrb7ZJLI1TTCIZx5/DxjLqM -Tt/RFBpVo9geZQrvLUqLAMwdaUvDXC2c6DaCPXTh65oCZj/hqzlJHH+RoTWWzKI+ -BjXxgUWF9EmZUBrg68DSmI+9wuDFsjZ51BcqvJwxyfxtTaWhdoYqH/UQS+D1FP3/ -diZHHlzwVwPICzM9ooNTgbrcDzyxRkIVqsVwBq7EtzcvgYUyX53yG25Giy6YQaQ2 -ZtQ/VymwFL3XdUWV6B/hU4PVAFvO3qlOtdJ6TpE+nEWgcWjCv5g7RjXX -=MuOY ------END PGP PUBLIC KEY BLOCK----- -""" -PRIVATE_KEY = """ ------BEGIN PGP PRIVATE KEY BLOCK----- -Version: GnuPG v1.4.10 (GNU/Linux) - -lQcYBFC9+dkBEADNRfwV23TWEoGc/x0wWH1P7PlXt8MnC2Z1kKaKKmfnglVrpOiz -iLWoiU58sfZ0L5vHkzXHXCBf6Eiy/EtUIvdiWAn+yASJ1mk5jZTBKO/WMAHD8wTO -zpMsFmWyg3xc4DkmFa9KQ5EVU0o/nqPeyQxNMQN7px5pPwrJtJFmPxnxm+aDkPYx -irDmz/4DeDNqXliazGJKw7efqBdlwTHkl9Akw2gwy178pmsKwHHEMOBOFFvX61AT -huKqHYmlCGSliwbrJppTG7jc1/ls3itrK+CWTg4txREkSpEVmfcASvw/ZqLbjgfs -d/INMwXnR9U81O8+7LT6yw/ca4ppcFoJD7/XJbkRiML6+bJ4Dakiy6i727BzV17g -wI1zqNvm5rAhtALKfACha6YO43aJzairO4II1wxVHvRDHZn2IuKDDephQ3Ii7/vb -hUOf6XCSmchkAcpKXUOvbxm1yfB1LRa64mMc2RcZxf4mW7KQkulBsdV5QG2276lv -U2UUy2IutXcGP5nXC+f6sJJGJeEToKJ57yiO/VWJFjKN8SvP+7AYsQSqINUuEf6H -T5gCPCraGMkTUTPXrREvu7NOohU78q6zZNaL3GW8ai7eSeANSuQ8Vzffx7Wd8Y7i -Pw9sYj0SMFs1UgjbuL6pO5ueHh+qyumbtAq2K0Bci0kqOcU4E9fNtdiovQARAQAB -AA/+JHtlL39G1wsH9R6UEfUQJGXR9MiIiwZoKcnRB2o8+DS+OLjg0JOh8XehtuCs -E/8oGQKtQqa5bEIstX7IZoYmYFiUQi9LOzIblmp2vxOm+HKkxa4JszWci2/ZmC3t -KtaA4adl9XVnshoQ7pijuCMUKB3naBEOAxd8s9d/JeReGIYkJErdrnVfNk5N71Ds -FmH5Ll3XtEDvgBUQP3nkA6QFjpsaB94FHjL3gDwum/cxzj6pCglcvHOzEhfY0Ddb -J967FozQTaf2JW3O+w3LOqtcKWpq87B7+O61tVidQPSSuzPjCtFF0D2LC9R/Hpky -KTMQ6CaKja4MPhjwywd4QPcHGYSqjMpflvJqi+kYIt8psUK/YswWjnr3r4fbuqVY -VhtiHvnBHQjz135lUqWvEz4hM3Xpnxydx7aRlv5NlevK8+YIO5oFbWbGNTWsPZI5 -jpoFBpSsnR1Q5tnvtNHauvoWV+XN2qAOBTG+/nEbDYH6Ak3aaE9jrpTdYh0CotYF -q7csANsDy3JvkAzeU6WnYpsHHaAjqOGyiZGsLej1UcXPFMosE/aUo4WQhiS8Zx2c -zOVKOi/X5vQ2GdNT9Qolz8AriwzsvFR+bxPzyd8V6ALwDsoXvwEYinYBKK8j0OPv -OOihSR6HVsuP9NUZNU9ewiGzte/+/r6pNXHvR7wTQ8EWLcEIAN6Zyrb0bHZTIlxt -VWur/Ht2mIZrBaO50qmM5RD3T5oXzWXi/pjLrIpBMfeZR9DWfwQwjYzwqi7pxtYx -nJvbMuY505rfnMoYxb4J+cpRXV8MS7Dr1vjjLVUC9KiwSbM3gg6emfd2yuA93ihv -Pe3mffzLIiQa4mRE3wtGcioC43nWuV2K2e1KjxeFg07JhrezA/1Cak505ab/tmvP -4YmjR5c44+yL/YcQ3HdFgs4mV+nVbptRXvRcPpolJsgxPccGNdvHhsoR4gwXMS3F -RRPD2z6x8xeN73Q4KH3bm01swQdwFBZbWVfmUGLxvN7leCdfs9+iFJyqHiCIB6Iv -mQfp8F0IAOwSo8JhWN+V1dwML4EkIrM8wUb4yecNLkyR6TpPH/qXx4PxVMC+vy6x -sCtjeHIwKE+9vqnlhd5zOYh7qYXEJtYwdeDDmDbL8oks1LFfd+FyAuZXY33DLwn0 -cRYsr2OEZmaajqUB3NVmj3H4uJBN9+paFHyFSXrH68K1Fk2o3n+RSf2EiX+eICwI -L6rqoF5sSVUghBWdNegV7qfy4anwTQwrIMGjgU5S6PKW0Dr/3iO5z3qQpGPAj5OW -ATqPWkDICLbObPxD5cJlyyNE2wCA9VVc6/1d6w4EVwSq9h3/WTpATEreXXxTGptd -LNiTA1nmakBYNO2Iyo3djhaqBdWjk+EIAKtVEnJH9FAVwWOvaj1RoZMA5DnDMo7e -SnhrCXl8AL7Z1WInEaybasTJXn1uQ8xY52Ua4b8cbuEKRKzw/70NesFRoMLYoHTO -dyeszvhoDHberpGRTciVmpMu7Hyi33rM31K9epA4ib6QbbCHnxkWOZB+Bhgj1hJ8 -xb4RBYWiWpAYcg0+DAC3w9gfxQhtUlZPIbmbrBmrVkO2GVGUj8kH6k4UV6kUHEGY -HQWQR0HcbKcXW81ZXCCD0l7ROuEWQtTe5Jw7dJ4/QFuqZnPutXVRNOZqpl6eRShw -7X2/a29VXBpmHA95a88rSQsL+qm7Fb3prqRmuMCtrUZgFz7HLSTuUMR867QcTGVh -cCBUZXN0IEtleSA8bGVhcEBsZWFwLnNlPokCNwQTAQgAIQUCUL352QIbAwULCQgH -AwUVCgkICwUWAgMBAAIeAQIXgAAKCRAvRV4oJNGN30+xEACh9yLkZ4jqW0/wwyIM -MI896MQf1tAwzMj16MJYUjrjNK4Bn57QaQW926HsxF8C/OjT0MTRhq7heYZJnnEo -rj0rzpkJapUveTRkKeoTRtGGigqJYfkOTU7KRVwgJBXIfaKlI3tC3cX0j0H1fVKX -hLxsj5pNSPRCVf2A5mePg44HtXe6oVWSJ8+EcdTa0shf03NhAtFaY0BbFGPSm9mA -QUe4rxugwXPLctIyV4uweFo5BXFBCb4kKTBdnQi3aJwnoWLNT6rDdTe4/nhY0Hfo -alTCYGLkhio77gBHwpTOjEMO/hZhcDMi4CvxMPw7bRxAwq4u+0j0pDhkiLcQs4U4 -Ou/fH+pia+1nF5h19cNVXIm+RX2fL0wxVYc/14AIAK3YT6PVev9XYEkogSj0P7Kb -HKOruYpnToXJBERNJZwGL1U+ihPNUyroRf29t7u8flnXsOpCtBEIWAO8Muy5pWjV -3O6zAUCfWetAieCQ7WrQVmdJDa7dlX3Qx1XagUzqZdAq2jVI1hOWDA2rKytnReSF -/A97rmLaWZ8aoNCs8i4NLcy9Lbzi9QtornYGVCEmTTym0tM9L/mn7gAJ8dqUwt7n -s24dibfElky4ZZeItD+D7OZGeh0FDuejvv2dXFqL1/pkHpGBZhEckg0fZ95NbgMC -4pSZkZnqRpr2GwfB5aFfB6sIIJ0HGARQvfnZARAAtCP8Z9bm6KzIA7wbXx9LBIcJ -1wQvOPf99s4nOrnQev9xH5PZ820qS9xUjrlyE2bGYAhz5Cmq56ENs7THErIdZHtQ -uYEBprO+VFZjP50vtmCOL2PDl/xgv6J9r1Mp3KnR/m0esR+YceDW1qX07IkB5s+Q -us80v5LmmxnWcikWmR7dt1kOyV/+M6Y6mwvfQ4x3D/QUpO7SfMCOG5DGA7hVUHU/ -Tuh8MihmMFFOLAEEQI+1wkxr1W09HaYCcB+EZqxLSaBwMeFoYPJie9dBNBgps39o -6pDbjsO+or4JNuyoHvh8NNI5iY0IR2NMlW4mqCcHEazys2koxFTYK6YD95Vz0RkA -K4BErCDk3lVR0PH4StmLU3gmPayIjvi9Dl9saPRyu4Xx2WVi+q6spl3ckn4c4f3+ -iD8hxVp74+wa5ew0fIXjIpMoHCar/nndsse4i8glCddINdiOPPmhI9Wi3nT+5Z2t -9omPP2dEh0CzR+j1zvUpT3KtmhVICqhO+QP9BTJOwrp81NTlq9mbUyzTtVk/9dy3 -zoYbhKvY08k6LJ9FsQYySqtfJZ4cwl5WsOhALWwOwlMLA9wkz0eemgFxStyOylzl -QKoIK7zHuU6XYOXa32KSPIWaLy+WgIG/u2ObWtdE3CXVIUuSt5BQFnv7XVNHJllD -Az9VDEkOSYOiSEFVoUsAEQEAAQAP/1AagnZQZyzHDEgw4QELAspYHCWLXE5aZInX -wTUJhK31IgIXNn9bJ0hFiSpQR2xeMs9oYtRuPOu0P8oOFMn4/z374fkjZy8QVY3e -PlL+3EUeqYtkMwlGNmVw5a/NbNuNfm5Darb7pEfbYd1gPcni4MAYw7R2SG/57GbC -9gucvspHIfOSfBNLBthDzmK8xEKe1yD2eimfc2T7IRYb6hmkYfeds5GsqvGI6mwI -85h4uUHWRc5JOlhVM6yX8hSWx0L60Z3DZLChmc8maWnFXd7C8eQ6P1azJJbW71Ih -7CoK0XW4LE82vlQurSRFgTwfl7wFYszW2bOzCuhHDDtYnwH86Nsu0DC78ZVRnvxn -E8Ke/AJgrdhIOo4UAyR+aZD2+2mKd7/waOUTUrUtTzc7i8N3YXGi/EIaNReBXaq+ -ZNOp24BlFzRp+FCF/pptDW9HjPdiV09x0DgICmeZS4Gq/4vFFIahWctg52NGebT0 -Idxngjj+xDtLaZlLQoOz0n5ByjO/Wi0ANmMv1sMKCHhGvdaSws2/PbMR2r4caj8m -KXpIgdinM/wUzHJ5pZyF2U/qejsRj8Kw8KH/tfX4JCLhiaP/mgeTuWGDHeZQERAT -xPmRFHaLP9/ZhvGNh6okIYtrKjWTLGoXvKLHcrKNisBLSq+P2WeFrlme1vjvJMo/ -jPwLT5o9CADQmcbKZ+QQ1ZM9v99iDZol7SAMZX43JC019sx6GK0u6xouJBcLfeB4 -OXacTgmSYdTa9RM9fbfVpti01tJ84LV2SyL/VJq/enJF4XQPSynT/tFTn1PAor6o -tEAAd8fjKdJ6LnD5wb92SPHfQfXqI84rFEO8rUNIE/1ErT6DYifDzVCbfD2KZdoF -cOSp7TpD77sY1bs74ocBX5ejKtd+aH99D78bJSMM4pSDZsIEwnomkBHTziubPwJb -OwnATy0LmSMAWOw5rKbsh5nfwCiUTM20xp0t5JeXd+wPVWbpWqI2EnkCEN+RJr9i -7dp/ymDQ+Yt5wrsN3NwoyiexPOG91WQVCADdErHsnglVZZq9Z8Wx7KwecGCUurJ2 -H6lKudv5YOxPnAzqZS5HbpZd/nRTMZh2rdXCr5m2YOuewyYjvM757AkmUpM09zJX -MQ1S67/UX2y8/74TcRF97Ncx9HeELs92innBRXoFitnNguvcO6Esx4BTe1OdU6qR -ER3zAmVf22Le9ciXbu24DN4mleOH+OmBx7X2PqJSYW9GAMTsRB081R6EWKH7romQ -waxFrZ4DJzZ9ltyosEJn5F32StyLrFxpcrdLUoEaclZCv2qka7sZvi0EvovDVEBU -e10jOx9AOwf8Gj2ufhquQ6qgVYCzbP+YrodtkFrXRS3IsljIchj1M2ffB/0bfoUs -rtER9pLvYzCjBPg8IfGLw0o754Qbhh/ReplCRTusP/fQMybvCvfxreS3oyEriu/G -GufRomjewZ8EMHDIgUsLcYo2UHZsfF7tcazgxMGmMvazp4r8vpgrvW/8fIN/6Adu -tF+WjWDTvJLFJCe6O+BFJOWrssNrrra1zGtLC1s8s+Wfpe+bGPL5zpHeebGTwH1U -22eqgJArlEKxrfarz7W5+uHZJHSjF/K9ZvunLGD0n9GOPMpji3UO3zeM8IYoWn7E -/EWK1XbjnssNemeeTZ+sDh+qrD7BOi+vCX1IyBxbfqnQfJZvmcPWpruy1UsO+aIC -0GY8Jr3OL69dDQ21jueJAh8EGAEIAAkFAlC9+dkCGwwACgkQL0VeKCTRjd9HCw/+ -LQSVgLLF4ulYlPCjWIIuQwrPbJfWUVVr2dPUFVM85DCv8gBzk5c121snXh9Swovm -laBbw6ate3BmbXLh64jVE9Za5sbTWi7PCcbO/bpRy4d6oLmitmNw6cq0vjTLxUYy -bwuiJxWREkfxuU85EKdouN062YDevH+/YResmlJrcCE7LRlJFeRlKsrrwBU3BqYd -GgFJjKjQC1peeQ9fj62Y7xfwE9+PXbkiWO5u/Bk8hb1VZH1SoIRU98NHVcp6BVvp -VK0jLAXuSauSczULmpRjbyt1lhaAqivDTWEEZXiNNbRyp17c3nVdPWOcgBr42hdQ -z25CgZgyLCsvu82wuXLKJblrIPJX3Yf+si6KqEWBsmwdOWybsjygaF5HvzgFqAAD -U0goPWoQ71PorP2XOUNp5ZLkBQp5etvtkksjVNMIhnHn8PGMuoxO39EUGlWj2B5l -Cu8tSosAzB1pS8NcLZzoNoI9dOHrmgJmP+GrOUkcf5GhNZbMoj4GNfGBRYX0SZlQ -GuDrwNKYj73C4MWyNnnUFyq8nDHJ/G1NpaF2hiof9RBL4PUU/f92JkceXPBXA8gL -Mz2ig1OButwPPLFGQhWqxXAGrsS3Ny+BhTJfnfIbbkaLLphBpDZm1D9XKbAUvdd1 -RZXoH+FTg9UAW87eqU610npOkT6cRaBxaMK/mDtGNdc= -=JTFu ------END PGP PRIVATE KEY BLOCK----- -""" - -# key 7FEE575A: public key "anotheruser " -PUBLIC_KEY_2 = """ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1.4.10 (GNU/Linux) - -mI0EUYwJXgEEAMbTKHuPJ5/Gk34l9Z06f+0WCXTDXdte1UBoDtZ1erAbudgC4MOR -gquKqoj3Hhw0/ILqJ88GcOJmKK/bEoIAuKaqlzDF7UAYpOsPZZYmtRfPC2pTCnXq -Z1vdeqLwTbUspqXflkCkFtfhGKMq5rH8GV5a3tXZkRWZhdNwhVXZagC3ABEBAAG0 -IWFub3RoZXJ1c2VyIDxhbm90aGVydXNlckBsZWFwLnNlPoi4BBMBAgAiBQJRjAle -AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRB/nfpof+5XWotuA/4tLN4E -gUr7IfLy2HkHAxzw7A4rqfMN92DIM9mZrDGaWRrOn3aVF7VU1UG7MDkHfPvp/cFw -ezoCw4s4IoHVc/pVlOkcHSyt4/Rfh248tYEJmFCJXGHpkK83VIKYJAithNccJ6Q4 -JE/o06Mtf4uh/cA1HUL4a4ceqUhtpLJULLeKo7iNBFGMCV4BBADsyQI7GR0wSAxz -VayLjuPzgT+bjbFeymIhjuxKIEwnIKwYkovztW+4bbOcQs785k3Lp6RzvigTpQQt -Z/hwcLOqZbZw8t/24+D+Pq9mMP2uUvCFFqLlVvA6D3vKSQ/XNN+YB919WQ04jh63 -yuRe94WenT1RJd6xU1aaUff4rKizuQARAQABiJ8EGAECAAkFAlGMCV4CGwwACgkQ -f536aH/uV1rPZQQAqCzRysOlu8ez7PuiBD4SebgRqWlxa1TF1ujzfLmuPivROZ2X -Kw5aQstxgGSjoB7tac49s0huh4X8XK+BtJBfU84JS8Jc2satlfwoyZ35LH6sDZck -I+RS/3we6zpMfHs3vvp9xgca6ZupQxivGtxlJs294TpJorx+mFFqbV17AzQ= -=Thdu ------END PGP PUBLIC KEY BLOCK----- -""" - -PRIVATE_KEY_2 = """ ------BEGIN PGP PRIVATE KEY BLOCK----- -Version: GnuPG v1.4.10 (GNU/Linux) - -lQHYBFGMCV4BBADG0yh7jyefxpN+JfWdOn/tFgl0w13bXtVAaA7WdXqwG7nYAuDD -kYKriqqI9x4cNPyC6ifPBnDiZiiv2xKCALimqpcwxe1AGKTrD2WWJrUXzwtqUwp1 -6mdb3Xqi8E21LKal35ZApBbX4RijKuax/BleWt7V2ZEVmYXTcIVV2WoAtwARAQAB -AAP7BLuSAx7tOohnimEs74ks8l/L6dOcsFQZj2bqs4AoY3jFe7bV0tHr4llypb/8 -H3/DYvpf6DWnCjyUS1tTnXSW8JXtx01BUKaAufSmMNg9blKV6GGHlT/Whe9uVyks -7XHk/+9mebVMNJ/kNlqq2k+uWqJohzC8WWLRK+d1tBeqDsECANZmzltPaqUsGV5X -C3zszE3tUBgptV/mKnBtopKi+VH+t7K6fudGcG+bAcZDUoH/QVde52mIIjjIdLje -uajJuHUCAO1mqh+vPoGv4eBLV7iBo3XrunyGXiys4a39eomhxTy3YktQanjjx+ty -GltAGCs5PbWGO6/IRjjvd46wh53kzvsCAO0J97gsWhzLuFnkxFAJSPk7RRlyl7lI -1XS/x0Og6j9XHCyY1OYkfBm0to3UlCfkgirzCYlTYObCofzdKFIPDmSqHbQhYW5v -dGhlcnVzZXIgPGFub3RoZXJ1c2VyQGxlYXAuc2U+iLgEEwECACIFAlGMCV4CGwMG -CwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEH+d+mh/7ldai24D/i0s3gSBSvsh -8vLYeQcDHPDsDiup8w33YMgz2ZmsMZpZGs6fdpUXtVTVQbswOQd8++n9wXB7OgLD -izgigdVz+lWU6RwdLK3j9F+Hbjy1gQmYUIlcYemQrzdUgpgkCK2E1xwnpDgkT+jT -oy1/i6H9wDUdQvhrhx6pSG2kslQst4qjnQHYBFGMCV4BBADsyQI7GR0wSAxzVayL -juPzgT+bjbFeymIhjuxKIEwnIKwYkovztW+4bbOcQs785k3Lp6RzvigTpQQtZ/hw -cLOqZbZw8t/24+D+Pq9mMP2uUvCFFqLlVvA6D3vKSQ/XNN+YB919WQ04jh63yuRe -94WenT1RJd6xU1aaUff4rKizuQARAQABAAP9EyElqJ3dq3EErXwwT4mMnbd1SrVC -rUJrNWQZL59mm5oigS00uIyR0SvusOr+UzTtd8ysRuwHy5d/LAZsbjQStaOMBILx -77TJveOel0a1QK0YSMF2ywZMCKvquvjli4hAtWYz/EwfuzQN3t23jc5ny+GqmqD2 -3FUxLJosFUfLNmECAO9KhVmJi+L9dswIs+2Dkjd1eiRQzNOEVffvYkGYZyKxNiXF -UA5kvyZcB4iAN9sWCybE4WHZ9jd4myGB0MPDGxkCAP1RsXJbbuD6zS7BXe5gwunO -2q4q7ptdSl/sJYQuTe1KNP5d/uGsvlcFfsYjpsopasPjFBIncc/2QThMKlhoEaEB -/0mVAxpT6SrEvUbJ18z7kna24SgMPr3OnPMxPGfvNLJY/Xv/A17YfoqjmByCvsKE -JCDjopXtmbcrZyoEZbEht9mko4ifBBgBAgAJBQJRjAleAhsMAAoJEH+d+mh/7lda -z2UEAKgs0crDpbvHs+z7ogQ+Enm4EalpcWtUxdbo83y5rj4r0TmdlysOWkLLcYBk -o6Ae7WnOPbNIboeF/FyvgbSQX1POCUvCXNrGrZX8KMmd+Sx+rA2XJCPkUv98Hus6 -THx7N776fcYHGumbqUMYrxrcZSbNveE6SaK8fphRam1dewM0 -=a5gs ------END PGP PRIVATE KEY BLOCK----- -""" -import unittest -if __name__ == "__main__": - unittest.main() -- cgit v1.2.3 From b00bb5cf4af7183076c02b02722ed6c495ff3f8a Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 30 May 2013 02:38:03 +0900 Subject: add data files: testing certificates --- MANIFEST.in | 1 + README.rst | 4 ++++ changes/bug_add-data-files | 1 + setup.py | 30 +++++++++++++++++++++--------- src/leap/common/__init__.py | 2 +- 5 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 MANIFEST.in create mode 100644 changes/bug_add-data-files diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..84a01ef --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include src/leap/common/testing/*.pem diff --git a/README.rst b/README.rst index f960b1f..aeb871f 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,9 @@ leap.common =========== + +.. image:: https://pypip.in/v/leap.common/badge.png + :target: https://crate.io/packages/leap.common + A collection of shared utils used by the several python LEAP subprojects. * leap.common.cert diff --git a/changes/bug_add-data-files b/changes/bug_add-data-files new file mode 100644 index 0000000..5231fb8 --- /dev/null +++ b/changes/bug_add-data-files @@ -0,0 +1 @@ + o Add data files to setup and manifest (certificates for tests) diff --git a/setup.py b/setup.py index 99fd25b..c6b7fab 100644 --- a/setup.py +++ b/setup.py @@ -32,31 +32,42 @@ requirements = [ "PyCrypto", ] - -dependency_links = [ - "https://protobuf-socket-rpc.googlecode.com/files/protobuf.socketrpc-1.3.2.tar.gz#egg=protobuf.socketrpc" -] +#dependency_links = [ + #"https://protobuf-socket-rpc.googlecode.com/files/protobuf.socketrpc-1.3.2.tar.gz#egg=protobuf.socketrpc" +#] tests_requirements = [ 'mock', ] - -# XXX add classifiers, docs +trove_classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + ("License :: OSI Approved :: GNU General " + "Public License v3 or later (GPLv3+)"), + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + "Topic :: Communications", + "Topic :: Security", + "Topic :: Utilities" +] setup( name='leap.common', # If you change version, do it also in # src/leap/common/__init__.py - version='0.2.4', + version='0.2.5', url='https://leap.se/', license='GPLv3+', author='The LEAP Encryption Access Project', author_email='info@leap.se', - description='Common files used by the LEAP Client project.', + description='Common files used by the LEAP project.', long_description=( "Common files used by the LEAP Client project." ), + classifiers=trove_classifiers, namespace_packages=["leap"], package_dir={'': 'src'}, # For now, we do not exclude tests because of the circular dependency @@ -65,6 +76,7 @@ setup( packages=find_packages('src'), test_suite='leap.common.tests', install_requires=requirements, - dependency_links=dependency_links, + #dependency_links=dependency_links, tests_require=tests_requirements, + include_package_data=True ) diff --git a/src/leap/common/__init__.py b/src/leap/common/__init__.py index 2821f12..2b30715 100644 --- a/src/leap/common/__init__.py +++ b/src/leap/common/__init__.py @@ -16,4 +16,4 @@ except ImportError: __all__ = ["certs", "check", "files", "events"] -__version__ = "0.2.4" +__version__ = "0.2.5" -- cgit v1.2.3 From 7f35ec61513d4286bde08ba957945b38b85ca87a Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 30 May 2013 10:05:13 -0300 Subject: Remove deps on soledad and python-gnupg. --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 99fd25b..7005b9e 100644 --- a/setup.py +++ b/setup.py @@ -27,8 +27,6 @@ requirements = [ 'protobuf.socketrpc', "PyOpenSSL", "python-dateutil", - "leap.soledad", - "python-gnupg", "PyCrypto", ] -- cgit v1.2.3 From 04e3a6e65bfea724dec8de47a7d8195ca3c3674c Mon Sep 17 00:00:00 2001 From: drebs Date: Wed, 5 Jun 2013 12:48:41 -0300 Subject: Move symmetric encryption code to leap.soledad. --- src/leap/common/crypto.py | 114 ----------------------------------- src/leap/common/tests/test_crypto.py | 88 --------------------------- 2 files changed, 202 deletions(-) delete mode 100644 src/leap/common/crypto.py delete mode 100644 src/leap/common/tests/test_crypto.py diff --git a/src/leap/common/crypto.py b/src/leap/common/crypto.py deleted file mode 100644 index 7f80a8a..0000000 --- a/src/leap/common/crypto.py +++ /dev/null @@ -1,114 +0,0 @@ -# -*- coding: utf-8 -*- -# crypto.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os -import binascii - -from Crypto.Cipher import AES -from Crypto.Util import Counter -from leap.common.check import leap_assert, leap_assert_type - -# -# encryption methods -# - -class EncryptionMethods(object): - """ - Representation of encryption methods that can be used. - """ - - AES_256_CTR = 'aes-256-ctr' - - -class UnknownEncryptionMethod(Exception): - """ - Raised when trying to encrypt/decrypt with unknown method. - """ - pass - - -# -# encrypt/decrypt functions -# - -# In the future, we might want to implement other encryption schemes and -# possibly factor out the actual encryption/decryption routines of the -# following functions to specific classes, while not changing the API. - -def encrypt_sym(data, key, method=EncryptionMethods.AES_256_CTR): - """ - Encrypt C{data} with C{key}, using C{method} encryption method. - - :param data: The data to be encrypted. - :type data: str - :param key: The key used to encrypt C{data} (must be 256 bits long). - :type key: str - :param method: The encryption method to use. - :type method: str - - :return: A tuple with the initial value and the encrypted data. - :rtype: (long, str) - """ - leap_assert_type(key, str) - - # AES-256 in CTR mode - if method == EncryptionMethods.AES_256_CTR: - leap_assert( - len(key) == 32, # 32 x 8 = 256 bits. - 'Wrong key size: %s bits (must be 256 bits long).' % (len(key)*8)) - iv = os.urandom(8) - ctr = Counter.new(64, prefix=iv) - cipher = AES.new(key=key, mode=AES.MODE_CTR, counter=ctr) - return binascii.b2a_base64(iv), cipher.encrypt(data) - - # raise if method is unknown - raise UnknownEncryptionMethod('Unkwnown method: %s' % method) - - -def decrypt_sym(data, key, method=EncryptionMethods.AES_256_CTR, **kwargs): - """ - Decrypt C{data} with C{key} using C{method} encryption method. - - :param data: The data to be decrypted. - :type data: str - :param key: The key used to decrypt C{data} (must be 256 bits long). - :type key: str - :param method: The encryption method to use. - :type method: str - :param kwargs: Other parameters specific to each encryption method. - :type kwargs: dict - - :return: The decrypted data. - :rtype: str - """ - leap_assert_type(key, str) - - # AES-256 in CTR mode - if method == EncryptionMethods.AES_256_CTR: - # assert params - leap_assert( - len(key) == 32, # 32 x 8 = 256 bits. - 'Wrong key size: %s (must be 256 bits long).' % len(key)) - leap_assert( - 'iv' in kwargs, - 'AES-256-CTR needs an initial value given as.') - ctr = Counter.new(64, prefix=binascii.a2b_base64(kwargs['iv'])) - cipher = AES.new(key=key, mode=AES.MODE_CTR, counter=ctr) - return cipher.decrypt(data) - - # raise if method is unknown - raise UnknownEncryptionMethod('Unkwnown method: %s' % method) diff --git a/src/leap/common/tests/test_crypto.py b/src/leap/common/tests/test_crypto.py deleted file mode 100644 index ae7dc71..0000000 --- a/src/leap/common/tests/test_crypto.py +++ /dev/null @@ -1,88 +0,0 @@ -## -*- coding: utf-8 -*- -# test_crypto.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -""" -Tests for the crypto submodule. -""" - - -import os -import binascii - - -from leap.common.testing.basetest import BaseLeapTest -from leap.common import crypto -from Crypto import Random - - -class CryptoTestCase(BaseLeapTest): - - def setUp(self): - pass - - def tearDown(self): - pass - - def test_encrypt_decrypt_sym(self): - # generate 256-bit key - key = Random.new().read(32) - iv, cyphertext = crypto.encrypt_sym( - 'data', key, - method=crypto.EncryptionMethods.AES_256_CTR) - self.assertTrue(cyphertext is not None) - self.assertTrue(cyphertext != '') - self.assertTrue(cyphertext != 'data') - plaintext = crypto.decrypt_sym( - cyphertext, key, iv=iv, - method=crypto.EncryptionMethods.AES_256_CTR) - self.assertEqual('data', plaintext) - - def test_decrypt_with_wrong_iv_fails(self): - key = Random.new().read(32) - iv, cyphertext = crypto.encrypt_sym( - 'data', key, - method=crypto.EncryptionMethods.AES_256_CTR) - self.assertTrue(cyphertext is not None) - self.assertTrue(cyphertext != '') - self.assertTrue(cyphertext != 'data') - # get a different iv by changing the first byte - rawiv = binascii.a2b_base64(iv) - wrongiv = rawiv - while wrongiv == rawiv: - wrongiv = os.urandom(1) + rawiv[1:] - plaintext = crypto.decrypt_sym( - cyphertext, key, iv=binascii.b2a_base64(wrongiv), - method=crypto.EncryptionMethods.AES_256_CTR) - self.assertNotEqual('data', plaintext) - - def test_decrypt_with_wrong_key_fails(self): - key = Random.new().read(32) - iv, cyphertext = crypto.encrypt_sym( - 'data', key, - method=crypto.EncryptionMethods.AES_256_CTR) - self.assertTrue(cyphertext is not None) - self.assertTrue(cyphertext != '') - self.assertTrue(cyphertext != 'data') - wrongkey = Random.new().read(32) # 256-bits key - # ensure keys are different in case we are extremely lucky - while wrongkey == key: - wrongkey = Random.new().read(32) - plaintext = crypto.decrypt_sym( - cyphertext, wrongkey, iv=iv, - method=crypto.EncryptionMethods.AES_256_CTR) - self.assertNotEqual('data', plaintext) -- cgit v1.2.3 From 7a60675f20c03a0afba931dcae6b48fa737efd57 Mon Sep 17 00:00:00 2001 From: drebs Date: Wed, 5 Jun 2013 23:26:08 -0300 Subject: Add changes file. --- changes/feature_move-symmetric-encryption-code-to-leap.soledad | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/feature_move-symmetric-encryption-code-to-leap.soledad diff --git a/changes/feature_move-symmetric-encryption-code-to-leap.soledad b/changes/feature_move-symmetric-encryption-code-to-leap.soledad new file mode 100644 index 0000000..1541362 --- /dev/null +++ b/changes/feature_move-symmetric-encryption-code-to-leap.soledad @@ -0,0 +1 @@ + o Move symmetric encryption code to leap.soledad. -- cgit v1.2.3 From a08b198d889396d25182bb9716817311bcc3be47 Mon Sep 17 00:00:00 2001 From: drebs Date: Sun, 9 Jun 2013 14:16:33 -0300 Subject: Add possibility of unregistering in events mechanism. --- changes/feature_add-events-ungerister | 1 + src/leap/common/events/__init__.py | 31 ++- src/leap/common/events/component.py | 55 +++- src/leap/common/events/events.proto | 8 + src/leap/common/events/events_pb2.py | 497 +++++++++++++++++++--------------- src/leap/common/events/server.py | 27 ++ src/leap/common/tests/test_events.py | 33 +++ 7 files changed, 435 insertions(+), 217 deletions(-) create mode 100644 changes/feature_add-events-ungerister diff --git a/changes/feature_add-events-ungerister b/changes/feature_add-events-ungerister new file mode 100644 index 0000000..0f17626 --- /dev/null +++ b/changes/feature_add-events-ungerister @@ -0,0 +1 @@ + o Add possibility of unregistering callbacks for a signal. diff --git a/src/leap/common/events/__init__.py b/src/leap/common/events/__init__.py index 9fc93ee..12416e4 100644 --- a/src/leap/common/events/__init__.py +++ b/src/leap/common/events/__init__.py @@ -24,7 +24,7 @@ import socket from leap.common.events import ( - events_pb2, + events_pb2 as proto, server, component, daemon, @@ -61,12 +61,37 @@ def register(signal, callback, uid=None, replace=False, reqcbk=None, :type timeout: int :return: the response from server for synch calls or nothing for asynch - calls + calls. :rtype: leap.common.events.events_pb2.EventsResponse or None """ return component.register(signal, callback, uid, replace, reqcbk, timeout) +def unregister(signal, uid=None, reqcbk=None, timeout=1000): + """ + Unregister a callback. + + If C{uid} is specified, unregisters only the callback identified by that + unique id. Otherwise, unregisters all callbacks registered for C{signal}. + + :param signal: the signal that causes the callback to be launched + :type signal: int (see the `events.proto` file) + :param uid: a unique id for the callback + :type uid: int + :param reqcbk: a callback to be called when a response from server is + received + :type reqcbk: function + callback(leap.common.events.events_pb2.EventResponse) + :param timeout: the timeout for synch calls + :type timeout: int + + :return: the response from server for synch calls or nothing for asynch + calls. + :rtype: leap.common.events.events_pb2.EventsResponse or None + """ + return component.unregister(signal, uid, reqcbk, timeout) + + def signal(signal, content="", mac_method="", mac="", reqcbk=None, timeout=1000): """ @@ -94,7 +119,7 @@ def signal(signal, content="", mac_method="", mac="", reqcbk=None, :type timeout: int :return: the response from server for synch calls or nothing for asynch - calls + calls. :rtype: leap.common.events.events_pb2.EventsResponse or None """ return component.signal(signal, content, mac_method, mac, reqcbk, timeout) diff --git a/src/leap/common/events/component.py b/src/leap/common/events/component.py index 9932190..029d1ac 100644 --- a/src/leap/common/events/component.py +++ b/src/leap/common/events/component.py @@ -56,6 +56,7 @@ class CallbackAlreadyRegistered(Exception): """ Raised when trying to register an already registered callback. """ + pass def ensure_component_daemon(): @@ -111,7 +112,7 @@ def register(signal, callback, uid=None, replace=False, reqcbk=None, callback identified by the given uid and replace is False. :return: the response from server for synch calls or nothing for asynch - calls + calls. :rtype: leap.common.events.events_pb2.EventsResponse or None """ ensure_component_daemon() # so we can receive registered signals @@ -140,6 +141,56 @@ def register(signal, callback, uid=None, replace=False, reqcbk=None, str(request)[:40]) return service.register(request, callback=reqcbk, timeout=timeout) +def unregister(signal, uid=None, reqcbk=None, timeout=1000): + """ + Unregister a callback. + + If C{uid} is specified, unregisters only the callback identified by that + unique id. Otherwise, unregisters all callbacks + + :param signal: the signal that causes the callback to be launched + :type signal: int (see the `events.proto` file) + :param uid: a unique id for the callback + :type uid: int + :param reqcbk: a callback to be called when a response from server is + received + :type reqcbk: function + callback(leap.common.events.events_pb2.EventResponse) + :param timeout: the timeout for synch calls + :type timeout: int + + :return: the response from server for synch calls or nothing for asynch + calls or None if no callback is registered for that signal or uid. + :rtype: leap.common.events.events_pb2.EventsResponse or None + """ + if signal not in registered_callbacks or not registered_callbacks[signal]: + logger.warning("No callback registered for signal %d." % signal) + return None + # unregister callback locally + cbklist = registered_callbacks[signal] + if uid is not None: + if filter(lambda (cbkuid, _): cbkuid == uid, cbklist) == []: + logger.warning("No callback registered for uid %d." % st) + return None + registered_callbacks[signal] = filter(lambda(x, y): x != uid, cbklist) + else: + # exclude all callbacks for given signal + registered_callbacks[signal] = [] + # unregister port in server if there are no more callbacks for this signal + if not registered_callbacks[signal]: + request = proto.UnregisterRequest() + request.event = signal + request.port = EventsComponentDaemon.get_instance().get_port() + request.mac_method = mac_auth.MacMethod.MAC_NONE + request.mac = "" + service = RpcService(proto.EventsServerService_Stub, + server.SERVER_PORT, 'localhost') + logger.info( + "Sending unregistration request to server on port %s: %s", + server.SERVER_PORT, + str(request)[:40]) + return service.unregister(request, callback=reqcbk, timeout=timeout) + def signal(signal, content="", mac_method="", mac="", reqcbk=None, timeout=1000): @@ -168,7 +219,7 @@ def signal(signal, content="", mac_method="", mac="", reqcbk=None, :type timeout: int :return: the response from server for synch calls or nothing for asynch - calls + calls. :rtype: leap.common.events.events_pb2.EventsResponse or None """ request = proto.SignalRequest() diff --git a/src/leap/common/events/events.proto b/src/leap/common/events/events.proto index 447b038..a813ed1 100644 --- a/src/leap/common/events/events.proto +++ b/src/leap/common/events/events.proto @@ -49,6 +49,13 @@ message RegisterRequest { required bytes mac = 4; } +message UnregisterRequest { + required Event event = 1; + required int32 port = 2; + required string mac_method = 3; + required bytes mac = 4; +} + message EventResponse { enum Status { @@ -63,6 +70,7 @@ message EventResponse { service EventsServerService { rpc register(RegisterRequest) returns (EventResponse); + rpc unregister(UnregisterRequest) returns (EventResponse); rpc signal(SignalRequest) returns (EventResponse); } diff --git a/src/leap/common/events/events_pb2.py b/src/leap/common/events/events_pb2.py index a4f1df4..5b1c118 100644 --- a/src/leap/common/events/events_pb2.py +++ b/src/leap/common/events/events_pb2.py @@ -1,85 +1,87 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! +# source: events.proto -from google.protobuf import descriptor -from google.protobuf import message -from google.protobuf import reflection -from google.protobuf import service +from google.protobuf.internal import enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import service as _service from google.protobuf import service_reflection from google.protobuf import descriptor_pb2 # @@protoc_insertion_point(imports) -DESCRIPTOR = descriptor.FileDescriptor( +DESCRIPTOR = _descriptor.FileDescriptor( name='events.proto', package='leap.common.events', - serialized_pb='\n\x0c\x65vents.proto\x12\x12leap.common.events\"\x97\x01\n\rSignalRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0f\n\x07\x63ontent\x18\x02 \x02(\t\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\x12\x12\n\nenc_method\x18\x05 \x01(\t\x12\x16\n\x0e\x65rror_occurred\x18\x06 \x01(\x08\"j\n\x0fRegisterRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0c\n\x04port\x18\x02 \x02(\x05\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\"\x82\x01\n\rEventResponse\x12\x38\n\x06status\x18\x01 \x02(\x0e\x32(.leap.common.events.EventResponse.Status\x12\x0e\n\x06result\x18\x02 \x01(\t\"\'\n\x06Status\x12\x06\n\x02OK\x10\x01\x12\n\n\x06UNAUTH\x10\x02\x12\t\n\x05\x45RROR\x10\x03*\xe7\x02\n\x05\x45vent\x12\x15\n\x11\x43LIENT_SESSION_ID\x10\x01\x12\x0e\n\nCLIENT_UID\x10\x02\x12\x19\n\x15SOLEDAD_CREATING_KEYS\x10\x03\x12\x1e\n\x1aSOLEDAD_DONE_CREATING_KEYS\x10\x04\x12\x1a\n\x16SOLEDAD_UPLOADING_KEYS\x10\x05\x12\x1f\n\x1bSOLEDAD_DONE_UPLOADING_KEYS\x10\x06\x12\x1c\n\x18SOLEDAD_DOWNLOADING_KEYS\x10\x07\x12!\n\x1dSOLEDAD_DONE_DOWNLOADING_KEYS\x10\x08\x12\x1c\n\x18SOLEDAD_NEW_DATA_TO_SYNC\x10\t\x12\x1a\n\x16SOLEDAD_DONE_DATA_SYNC\x10\n\x12\x17\n\x13UPDATER_NEW_UPDATES\x10\x0b\x12\x19\n\x15UPDATER_DONE_UPDATING\x10\x0c\x12\x10\n\x0cRAISE_WINDOW\x10\r2\xb9\x01\n\x13\x45ventsServerService\x12R\n\x08register\x12#.leap.common.events.RegisterRequest\x1a!.leap.common.events.EventResponse\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponse2h\n\x16\x45ventsComponentService\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponseB\x03\x90\x01\x01') + serialized_pb='\n\x0c\x65vents.proto\x12\x12leap.common.events\"\x97\x01\n\rSignalRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0f\n\x07\x63ontent\x18\x02 \x02(\t\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\x12\x12\n\nenc_method\x18\x05 \x01(\t\x12\x16\n\x0e\x65rror_occurred\x18\x06 \x01(\x08\"j\n\x0fRegisterRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0c\n\x04port\x18\x02 \x02(\x05\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\"l\n\x11UnregisterRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0c\n\x04port\x18\x02 \x02(\x05\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\"\x82\x01\n\rEventResponse\x12\x38\n\x06status\x18\x01 \x02(\x0e\x32(.leap.common.events.EventResponse.Status\x12\x0e\n\x06result\x18\x02 \x01(\t\"\'\n\x06Status\x12\x06\n\x02OK\x10\x01\x12\n\n\x06UNAUTH\x10\x02\x12\t\n\x05\x45RROR\x10\x03*\xe7\x02\n\x05\x45vent\x12\x15\n\x11\x43LIENT_SESSION_ID\x10\x01\x12\x0e\n\nCLIENT_UID\x10\x02\x12\x19\n\x15SOLEDAD_CREATING_KEYS\x10\x03\x12\x1e\n\x1aSOLEDAD_DONE_CREATING_KEYS\x10\x04\x12\x1a\n\x16SOLEDAD_UPLOADING_KEYS\x10\x05\x12\x1f\n\x1bSOLEDAD_DONE_UPLOADING_KEYS\x10\x06\x12\x1c\n\x18SOLEDAD_DOWNLOADING_KEYS\x10\x07\x12!\n\x1dSOLEDAD_DONE_DOWNLOADING_KEYS\x10\x08\x12\x1c\n\x18SOLEDAD_NEW_DATA_TO_SYNC\x10\t\x12\x1a\n\x16SOLEDAD_DONE_DATA_SYNC\x10\n\x12\x17\n\x13UPDATER_NEW_UPDATES\x10\x0b\x12\x19\n\x15UPDATER_DONE_UPDATING\x10\x0c\x12\x10\n\x0cRAISE_WINDOW\x10\r2\x91\x02\n\x13\x45ventsServerService\x12R\n\x08register\x12#.leap.common.events.RegisterRequest\x1a!.leap.common.events.EventResponse\x12V\n\nunregister\x12%.leap.common.events.UnregisterRequest\x1a!.leap.common.events.EventResponse\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponse2h\n\x16\x45ventsComponentService\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponseB\x03\x90\x01\x01') -_EVENT = descriptor.EnumDescriptor( +_EVENT = _descriptor.EnumDescriptor( name='Event', full_name='leap.common.events.Event', filename=None, file=DESCRIPTOR, values=[ - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='CLIENT_SESSION_ID', index=0, number=1, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='CLIENT_UID', index=1, number=2, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='SOLEDAD_CREATING_KEYS', index=2, number=3, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='SOLEDAD_DONE_CREATING_KEYS', index=3, number=4, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='SOLEDAD_UPLOADING_KEYS', index=4, number=5, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='SOLEDAD_DONE_UPLOADING_KEYS', index=5, number=6, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='SOLEDAD_DOWNLOADING_KEYS', index=6, number=7, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='SOLEDAD_DONE_DOWNLOADING_KEYS', index=7, number=8, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='SOLEDAD_NEW_DATA_TO_SYNC', index=8, number=9, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='SOLEDAD_DONE_DATA_SYNC', index=9, number=10, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='UPDATER_NEW_UPDATES', index=10, number=11, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='UPDATER_DONE_UPDATING', index=11, number=12, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='RAISE_WINDOW', index=12, number=13, options=None, type=None), ], containing_type=None, options=None, - serialized_start=432, - serialized_end=791, + serialized_start=542, + serialized_end=901, ) - +Event = enum_type_wrapper.EnumTypeWrapper(_EVENT) CLIENT_SESSION_ID = 1 CLIENT_UID = 2 SOLEDAD_CREATING_KEYS = 3 @@ -95,240 +97,311 @@ UPDATER_DONE_UPDATING = 12 RAISE_WINDOW = 13 -_EVENTRESPONSE_STATUS = descriptor.EnumDescriptor( +_EVENTRESPONSE_STATUS = _descriptor.EnumDescriptor( name='Status', full_name='leap.common.events.EventResponse.Status', filename=None, file=DESCRIPTOR, values=[ - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='OK', index=0, number=1, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='UNAUTH', index=1, number=2, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='ERROR', index=2, number=3, options=None, type=None), ], containing_type=None, options=None, - serialized_start=390, - serialized_end=429, + serialized_start=500, + serialized_end=539, ) -_SIGNALREQUEST = descriptor.Descriptor( +_SIGNALREQUEST = _descriptor.Descriptor( name='SignalRequest', full_name='leap.common.events.SignalRequest', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='event', full_name='leap.common.events.SignalRequest.event', index=0, number=1, type=14, cpp_type=8, label=2, has_default_value=False, default_value=1, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - descriptor.FieldDescriptor( - name='content', full_name='leap.common.events.SignalRequest.content', index=1, - number=2, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - descriptor.FieldDescriptor( - name='mac_method', full_name='leap.common.events.SignalRequest.mac_method', index=2, - number=3, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - descriptor.FieldDescriptor( - name='mac', full_name='leap.common.events.SignalRequest.mac', index=3, - number=4, type=12, cpp_type=9, label=2, - has_default_value=False, default_value="", - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - descriptor.FieldDescriptor( - name='enc_method', full_name='leap.common.events.SignalRequest.enc_method', index=4, - number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=unicode("", "utf-8"), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - descriptor.FieldDescriptor( - name='error_occurred', full_name='leap.common.events.SignalRequest.error_occurred', index=5, - number=6, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - extension_ranges=[], - serialized_start=37, - serialized_end=188, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='content', full_name='leap.common.events.SignalRequest.content', index=1, + number=2, type=9, cpp_type=9, label=2, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='mac_method', full_name='leap.common.events.SignalRequest.mac_method', index=2, + number=3, type=9, cpp_type=9, label=2, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='mac', full_name='leap.common.events.SignalRequest.mac', index=3, + number=4, type=12, cpp_type=9, label=2, + has_default_value=False, default_value="", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='enc_method', full_name='leap.common.events.SignalRequest.enc_method', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='error_occurred', full_name='leap.common.events.SignalRequest.error_occurred', index=5, + number=6, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + extension_ranges=[], + serialized_start=37, + serialized_end=188, +) + + +_REGISTERREQUEST = _descriptor.Descriptor( + name='RegisterRequest', + full_name='leap.common.events.RegisterRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='event', full_name='leap.common.events.RegisterRequest.event', index=0, + number=1, type=14, cpp_type=8, label=2, + has_default_value=False, default_value=1, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='port', full_name='leap.common.events.RegisterRequest.port', index=1, + number=2, type=5, cpp_type=1, label=2, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='mac_method', full_name='leap.common.events.RegisterRequest.mac_method', index=2, + number=3, type=9, cpp_type=9, label=2, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='mac', full_name='leap.common.events.RegisterRequest.mac', index=3, + number=4, type=12, cpp_type=9, label=2, + has_default_value=False, default_value="", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + extension_ranges=[], + serialized_start=190, + serialized_end=296, ) -_REGISTERREQUEST = descriptor.Descriptor( - name='RegisterRequest', - full_name='leap.common.events.RegisterRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - descriptor.FieldDescriptor( - name='event', full_name='leap.common.events.RegisterRequest.event', index=0, - number=1, type=14, cpp_type=8, label=2, - has_default_value=False, default_value=1, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - descriptor.FieldDescriptor( - name='port', full_name='leap.common.events.RegisterRequest.port', index=1, - number=2, type=5, cpp_type=1, label=2, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - descriptor.FieldDescriptor( - name='mac_method', full_name='leap.common.events.RegisterRequest.mac_method', index=2, - number=3, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - descriptor.FieldDescriptor( - name='mac', full_name='leap.common.events.RegisterRequest.mac', index=3, - number=4, type=12, cpp_type=9, label=2, - has_default_value=False, default_value="", - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - extension_ranges=[], - serialized_start=190, - serialized_end=296, +_UNREGISTERREQUEST = _descriptor.Descriptor( + name='UnregisterRequest', + full_name='leap.common.events.UnregisterRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='event', full_name='leap.common.events.UnregisterRequest.event', index=0, + number=1, type=14, cpp_type=8, label=2, + has_default_value=False, default_value=1, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='port', full_name='leap.common.events.UnregisterRequest.port', index=1, + number=2, type=5, cpp_type=1, label=2, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='mac_method', full_name='leap.common.events.UnregisterRequest.mac_method', index=2, + number=3, type=9, cpp_type=9, label=2, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='mac', full_name='leap.common.events.UnregisterRequest.mac', index=3, + number=4, type=12, cpp_type=9, label=2, + has_default_value=False, default_value="", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + extension_ranges=[], + serialized_start=298, + serialized_end=406, ) -_EVENTRESPONSE = descriptor.Descriptor( - name='EventResponse', - full_name='leap.common.events.EventResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - descriptor.FieldDescriptor( - name='status', full_name='leap.common.events.EventResponse.status', index=0, - number=1, type=14, cpp_type=8, label=2, - has_default_value=False, default_value=1, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - descriptor.FieldDescriptor( - name='result', full_name='leap.common.events.EventResponse.result', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=unicode("", "utf-8"), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _EVENTRESPONSE_STATUS, - ], - options=None, - is_extendable=False, - extension_ranges=[], - serialized_start=299, - serialized_end=429, +_EVENTRESPONSE = _descriptor.Descriptor( + name='EventResponse', + full_name='leap.common.events.EventResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='leap.common.events.EventResponse.status', index=0, + number=1, type=14, cpp_type=8, label=2, + has_default_value=False, default_value=1, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='result', full_name='leap.common.events.EventResponse.result', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _EVENTRESPONSE_STATUS, + ], + options=None, + is_extendable=False, + extension_ranges=[], + serialized_start=409, + serialized_end=539, ) _SIGNALREQUEST.fields_by_name['event'].enum_type = _EVENT _REGISTERREQUEST.fields_by_name['event'].enum_type = _EVENT +_UNREGISTERREQUEST.fields_by_name['event'].enum_type = _EVENT _EVENTRESPONSE.fields_by_name['status'].enum_type = _EVENTRESPONSE_STATUS -_EVENTRESPONSE_STATUS.containing_type = _EVENTRESPONSE; +_EVENTRESPONSE_STATUS.containing_type = _EVENTRESPONSE DESCRIPTOR.message_types_by_name['SignalRequest'] = _SIGNALREQUEST DESCRIPTOR.message_types_by_name['RegisterRequest'] = _REGISTERREQUEST +DESCRIPTOR.message_types_by_name['UnregisterRequest'] = _UNREGISTERREQUEST DESCRIPTOR.message_types_by_name['EventResponse'] = _EVENTRESPONSE -class SignalRequest(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType +class SignalRequest(_message.Message): + __metaclass__ = _reflection.GeneratedProtocolMessageType DESCRIPTOR = _SIGNALREQUEST # @@protoc_insertion_point(class_scope:leap.common.events.SignalRequest) -class RegisterRequest(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType +class RegisterRequest(_message.Message): + __metaclass__ = _reflection.GeneratedProtocolMessageType DESCRIPTOR = _REGISTERREQUEST # @@protoc_insertion_point(class_scope:leap.common.events.RegisterRequest) -class EventResponse(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType +class UnregisterRequest(_message.Message): + __metaclass__ = _reflection.GeneratedProtocolMessageType + DESCRIPTOR = _UNREGISTERREQUEST + + # @@protoc_insertion_point(class_scope:leap.common.events.UnregisterRequest) + + +class EventResponse(_message.Message): + __metaclass__ = _reflection.GeneratedProtocolMessageType DESCRIPTOR = _EVENTRESPONSE # @@protoc_insertion_point(class_scope:leap.common.events.EventResponse) -_EVENTSSERVERSERVICE = descriptor.ServiceDescriptor( - name='EventsServerService', - full_name='leap.common.events.EventsServerService', - file=DESCRIPTOR, - index=0, - options=None, - serialized_start=794, - serialized_end=979, - methods=[ - descriptor.MethodDescriptor( - name='register', - full_name='leap.common.events.EventsServerService.register', +DESCRIPTOR.has_options = True +DESCRIPTOR._options = _descriptor._ParseOptions( + descriptor_pb2.FileOptions(), '\220\001\001') + +_EVENTSSERVERSERVICE = _descriptor.ServiceDescriptor( + name='EventsServerService', + full_name='leap.common.events.EventsServerService', + file=DESCRIPTOR, index=0, - containing_service=None, - input_type=_REGISTERREQUEST, - output_type=_EVENTRESPONSE, options=None, - ), - descriptor.MethodDescriptor( - name='signal', - full_name='leap.common.events.EventsServerService.signal', - index=1, - containing_service=None, - input_type=_SIGNALREQUEST, - output_type=_EVENTRESPONSE, - options=None, - ), -]) + serialized_start=904, + serialized_end=1177, + methods=[ + _descriptor.MethodDescriptor( + name='register', + full_name='leap.common.events.EventsServerService.register', + index=0, + containing_service=None, + input_type=_REGISTERREQUEST, + output_type=_EVENTRESPONSE, + options=None, + ), + _descriptor.MethodDescriptor( + name='unregister', + full_name='leap.common.events.EventsServerService.unregister', + index=1, + containing_service=None, + input_type=_UNREGISTERREQUEST, + output_type=_EVENTRESPONSE, + options=None, + ), + _descriptor.MethodDescriptor( + name='signal', + full_name='leap.common.events.EventsServerService.signal', + index=2, + containing_service=None, + input_type=_SIGNALREQUEST, + output_type=_EVENTRESPONSE, + options=None, + ), + ]) -class EventsServerService(service.Service): +class EventsServerService(_service.Service): __metaclass__ = service_reflection.GeneratedServiceType DESCRIPTOR = _EVENTSSERVERSERVICE @@ -338,28 +411,28 @@ class EventsServerService_Stub(EventsServerService): DESCRIPTOR = _EVENTSSERVERSERVICE -_EVENTSCOMPONENTSERVICE = descriptor.ServiceDescriptor( - name='EventsComponentService', - full_name='leap.common.events.EventsComponentService', - file=DESCRIPTOR, - index=1, - options=None, - serialized_start=981, - serialized_end=1085, - methods=[ - descriptor.MethodDescriptor( - name='signal', - full_name='leap.common.events.EventsComponentService.signal', - index=0, - containing_service=None, - input_type=_SIGNALREQUEST, - output_type=_EVENTRESPONSE, +_EVENTSCOMPONENTSERVICE = _descriptor.ServiceDescriptor( + name='EventsComponentService', + full_name='leap.common.events.EventsComponentService', + file=DESCRIPTOR, + index=1, options=None, - ), -]) + serialized_start=1179, + serialized_end=1283, + methods=[ + _descriptor.MethodDescriptor( + name='signal', + full_name='leap.common.events.EventsComponentService.signal', + index=0, + containing_service=None, + input_type=_SIGNALREQUEST, + output_type=_EVENTRESPONSE, + options=None, + ), + ]) -class EventsComponentService(service.Service): +class EventsComponentService(_service.Service): __metaclass__ = service_reflection.GeneratedServiceType DESCRIPTOR = _EVENTSCOMPONENTSERVICE diff --git a/src/leap/common/events/server.py b/src/leap/common/events/server.py index 1f3a874..d53c218 100644 --- a/src/leap/common/events/server.py +++ b/src/leap/common/events/server.py @@ -100,6 +100,33 @@ class EventsServerService(proto.EventsServerService): response.status = proto.EventResponse.OK done.run(response) + def unregister(self, controller, request, done): + """ + Unregister a component port so it will not be signaled when specific + events come in. + + :param controller: used to mediate a single method call + :type controller: protobuf.socketrpc.controller.SocketRpcController + :param request: the request received from the component + :type request: leap.common.events.events_pb2.RegisterRequest + :param done: callback to be called when done + :type done: protobuf.socketrpc.server.Callback + """ + logger.info( + "Received unregistration request: %s..." % str(request)[:40]) + # remove component port from signal list + response = proto.EventResponse() + if request.event in registered_components: + try: + registered_components[request.event].remove(request.port) + response.status = proto.EventResponse.OK + except KeyError: + response.status = proto.EventsResponse.ERROR + response.result = 'Port %d not registered.' % request.port + # send response back to component + logger.debug('sending response back') + done.run(response) + def signal(self, controller, request, done): """ Perform an RPC call to signal all components registered to receive a diff --git a/src/leap/common/tests/test_events.py b/src/leap/common/tests/test_events.py index 8c0bd36..7286bdc 100644 --- a/src/leap/common/tests/test_events.py +++ b/src/leap/common/tests/test_events.py @@ -198,3 +198,36 @@ class EventsTestCase(unittest.TestCase): response = events.signal(sig) self.assertTrue(response.status == response.OK, 'Received wrong response status when signaling.') + + def test_component_unregister_all(self): + """ + Test that the component can unregister all events for one signal. + """ + sig = CLIENT_UID + complist = server.registered_components + events.register(sig, lambda x: True) + events.register(sig, lambda x: True) + time.sleep(0.1) + events.unregister(sig) + time.sleep(0.1) + port = component.EventsComponentDaemon.get_instance().get_port() + self.assertFalse(bool(complist[sig])) + self.assertTrue(port not in complist[sig]) + + def test_component_unregister_by_uid(self): + """ + Test that the component can unregister an event by uid. + """ + sig = CLIENT_UID + complist = server.registered_components + events.register(sig, lambda x: True, uid='cbkuid') + events.register(sig, lambda x: True, uid='cbkuid2') + time.sleep(0.1) + events.unregister(sig, uid='cbkuid') + time.sleep(0.1) + port = component.EventsComponentDaemon.get_instance().get_port() + self.assertTrue(sig in complist) + self.assertTrue(len(complist[sig]) == 1) + self.assertTrue( + component.registered_callbacks[sig].pop()[0] == 'cbkuid2') + self.assertTrue(port in complist[sig]) -- cgit v1.2.3 From 3a4906638909729236b693fb21a4b1b7194344b5 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 27 Jun 2013 15:33:57 -0300 Subject: Bugfix: use the provider's default language as default string Also take care (and note) a possible case with a problematic provider misconfiguration. --- changes/bug-3029_default-localization-language | 2 ++ src/leap/common/config/baseconfig.py | 23 ++++++++++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 changes/bug-3029_default-localization-language diff --git a/changes/bug-3029_default-localization-language b/changes/bug-3029_default-localization-language new file mode 100644 index 0000000..1fbb464 --- /dev/null +++ b/changes/bug-3029_default-localization-language @@ -0,0 +1,2 @@ + o Bugfix: use the provider's default language as default string. Also take care (and note) a possible case with a problematic provider +misconfiguration. Closes #3029. diff --git a/src/leap/common/config/baseconfig.py b/src/leap/common/config/baseconfig.py index e6bd9c4..699d734 100644 --- a/src/leap/common/config/baseconfig.py +++ b/src/leap/common/config/baseconfig.py @@ -155,26 +155,39 @@ class LocalizedKey(object): def __init__(self, func, **kwargs): self._func = func - def __call__(self, instance, lang="en"): + def __call__(self, instance, lang=None): """ Tries to return the string for the specified language, otherwise - informs the problem and returns an empty string. + returns the default language string. :param lang: language code :type lang: str :return: localized value from the possible values returned by self._func + It returns None in case that the provider does not provides + a matching pair of default_language and string for + that language. + e.g.: + 'default_language': 'es', + 'description': {'en': 'test description'} + Note that the json schema can't check that. """ descriptions = self._func(instance) - description_lang = "" - config_lang = "en" + config_lang = instance.get_default_language() + if lang is None: + lang = config_lang + for key in descriptions.keys(): if lang.startswith(key): config_lang = key break - description_lang = descriptions[config_lang] + description_lang = descriptions.get(config_lang) + if description_lang is None: + logger.error("There is a misconfiguration in the " + "provider's language strings.") + return description_lang def __get__(self, instance, instancetype): -- cgit v1.2.3 From 396de6ae9ec3c05c9ce8c48f0d74021773b22102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 28 Jun 2013 14:50:28 -0300 Subject: Fold in changes for 0.2.2 --- CHANGELOG | 21 +++++++++++++++++++++ changes/bug-3029_default-localization-language | 2 -- changes/bug_add-data-files | 1 - changes/bug_allow-absolute-paths | 1 - changes/bug_fix-deprecation-warning | 1 - ...fix-do-not-attempt-to-fetch-privkeys-from-server | 1 - changes/bug_fix-imports | 1 - changes/feature_add-events-ungerister | 1 - changes/feature_events_signals | 1 - changes/feature_improve_which | 2 -- changes/feature_key-manager | 1 - ...e_move-symmetric-encryption-code-to-leap.soledad | 1 - changes/feature_openpgp-context-manager | 1 - changes/feature_openpgp-sign-verify | 1 - changes/feature_raise-window-event | 1 - .../feature_use-pycrypto-for-symmetric-encryption | 1 - 16 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 CHANGELOG delete mode 100644 changes/bug-3029_default-localization-language delete mode 100644 changes/bug_add-data-files delete mode 100644 changes/bug_allow-absolute-paths delete mode 100644 changes/bug_fix-deprecation-warning delete mode 100644 changes/bug_fix-do-not-attempt-to-fetch-privkeys-from-server delete mode 100644 changes/bug_fix-imports delete mode 100644 changes/feature_add-events-ungerister delete mode 100644 changes/feature_events_signals delete mode 100644 changes/feature_improve_which delete mode 100644 changes/feature_key-manager delete mode 100644 changes/feature_move-symmetric-encryption-code-to-leap.soledad delete mode 100644 changes/feature_openpgp-context-manager delete mode 100644 changes/feature_openpgp-sign-verify delete mode 100644 changes/feature_raise-window-event delete mode 100644 changes/feature_use-pycrypto-for-symmetric-encryption diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..5f9cb69 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,21 @@ +0.2.2 Jun 28: + o Bugfix: use the provider's default language as default + string. Also take care (and note) a possible case with a + problematic provider misconfiguration. Closes #3029. + o Add data files to setup and manifest (certificates for tests) + o Allow absolute paths in baseconfig.load + o Fix deprecation warnings + o Fix attempt to fetch private keys from server. + o Fix missing imports + o Add possibility of unregistering callbacks for a signal. + o Add a mechanism for events signaling between components. + o Prioritize the path_extension in the which method so it finds our + bundled app before the system one, if any. + o Move the Key Manager to leap client repository. + o Move symmetric encryption code to leap.soledad. + o Refactor opengpg utility functions implementation so it uses a + context manager. + o Add OpenPGP sign/verify + o Add RAISE_WINDOW event + o Add AES-256 (CTR mode) encrypting/decrypting functions using + PyCrypto. diff --git a/changes/bug-3029_default-localization-language b/changes/bug-3029_default-localization-language deleted file mode 100644 index 1fbb464..0000000 --- a/changes/bug-3029_default-localization-language +++ /dev/null @@ -1,2 +0,0 @@ - o Bugfix: use the provider's default language as default string. Also take care (and note) a possible case with a problematic provider -misconfiguration. Closes #3029. diff --git a/changes/bug_add-data-files b/changes/bug_add-data-files deleted file mode 100644 index 5231fb8..0000000 --- a/changes/bug_add-data-files +++ /dev/null @@ -1 +0,0 @@ - o Add data files to setup and manifest (certificates for tests) diff --git a/changes/bug_allow-absolute-paths b/changes/bug_allow-absolute-paths deleted file mode 100644 index deaff1f..0000000 --- a/changes/bug_allow-absolute-paths +++ /dev/null @@ -1 +0,0 @@ - o Allow absolute paths in baseconfig.load diff --git a/changes/bug_fix-deprecation-warning b/changes/bug_fix-deprecation-warning deleted file mode 100644 index ac58117..0000000 --- a/changes/bug_fix-deprecation-warning +++ /dev/null @@ -1 +0,0 @@ - o Fix deprecation warnings diff --git a/changes/bug_fix-do-not-attempt-to-fetch-privkeys-from-server b/changes/bug_fix-do-not-attempt-to-fetch-privkeys-from-server deleted file mode 100644 index 4c8c0eb..0000000 --- a/changes/bug_fix-do-not-attempt-to-fetch-privkeys-from-server +++ /dev/null @@ -1 +0,0 @@ - o Fix attempt to fetch private keys from server. diff --git a/changes/bug_fix-imports b/changes/bug_fix-imports deleted file mode 100644 index 509f2f4..0000000 --- a/changes/bug_fix-imports +++ /dev/null @@ -1 +0,0 @@ - o Fix missing imports diff --git a/changes/feature_add-events-ungerister b/changes/feature_add-events-ungerister deleted file mode 100644 index 0f17626..0000000 --- a/changes/feature_add-events-ungerister +++ /dev/null @@ -1 +0,0 @@ - o Add possibility of unregistering callbacks for a signal. diff --git a/changes/feature_events_signals b/changes/feature_events_signals deleted file mode 100644 index ae0b7b0..0000000 --- a/changes/feature_events_signals +++ /dev/null @@ -1 +0,0 @@ - o Add a mechanism for events signaling between components. diff --git a/changes/feature_improve_which b/changes/feature_improve_which deleted file mode 100644 index d1d1fb5..0000000 --- a/changes/feature_improve_which +++ /dev/null @@ -1,2 +0,0 @@ - o Prioritize the path_extension in the which method so it finds our bundled - app before the system one, if any. diff --git a/changes/feature_key-manager b/changes/feature_key-manager deleted file mode 100644 index 47a62ed..0000000 --- a/changes/feature_key-manager +++ /dev/null @@ -1 +0,0 @@ - o Move the Key Manager to leap client repository. diff --git a/changes/feature_move-symmetric-encryption-code-to-leap.soledad b/changes/feature_move-symmetric-encryption-code-to-leap.soledad deleted file mode 100644 index 1541362..0000000 --- a/changes/feature_move-symmetric-encryption-code-to-leap.soledad +++ /dev/null @@ -1 +0,0 @@ - o Move symmetric encryption code to leap.soledad. diff --git a/changes/feature_openpgp-context-manager b/changes/feature_openpgp-context-manager deleted file mode 100644 index 4dbf759..0000000 --- a/changes/feature_openpgp-context-manager +++ /dev/null @@ -1 +0,0 @@ - o Refactor opengpg utility functions implementation so it uses a context manager. diff --git a/changes/feature_openpgp-sign-verify b/changes/feature_openpgp-sign-verify deleted file mode 100644 index 9422edc..0000000 --- a/changes/feature_openpgp-sign-verify +++ /dev/null @@ -1 +0,0 @@ - o Add OpenPGP sign/verify diff --git a/changes/feature_raise-window-event b/changes/feature_raise-window-event deleted file mode 100644 index 382ff3f..0000000 --- a/changes/feature_raise-window-event +++ /dev/null @@ -1 +0,0 @@ - o Add RAISE_WINDOW event diff --git a/changes/feature_use-pycrypto-for-symmetric-encryption b/changes/feature_use-pycrypto-for-symmetric-encryption deleted file mode 100644 index 5448483..0000000 --- a/changes/feature_use-pycrypto-for-symmetric-encryption +++ /dev/null @@ -1 +0,0 @@ - o Add AES-256 (CTR mode) encrypting/decrypting functions using PyCrypto. -- cgit v1.2.3