2011-09-25 16:39:03 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
#
|
|
|
|
# Copyright 2011 Stef Walter
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Lesser General Public License as published
|
|
|
|
# by the Free Software Foundation; either version 2 of the licence or (at
|
|
|
|
# your option) any later version.
|
|
|
|
#
|
|
|
|
# See the included COPYING file for more information.
|
|
|
|
#
|
|
|
|
|
2012-01-11 06:44:32 +00:00
|
|
|
import getopt
|
2011-09-25 16:39:03 +00:00
|
|
|
import os
|
|
|
|
import sys
|
2012-01-11 06:44:32 +00:00
|
|
|
import time
|
2011-09-25 16:39:03 +00:00
|
|
|
import unittest
|
|
|
|
|
|
|
|
import aes
|
|
|
|
import dh
|
|
|
|
import hkdf
|
|
|
|
|
|
|
|
import dbus
|
|
|
|
import dbus.service
|
|
|
|
import dbus.glib
|
|
|
|
import gobject
|
|
|
|
|
2011-11-12 07:08:12 +00:00
|
|
|
COLLECTION_PREFIX = "/org/freedesktop/secrets/collection/"
|
|
|
|
|
2011-09-25 16:39:03 +00:00
|
|
|
bus_name = 'org.freedesktop.Secret.MockService'
|
2012-01-23 08:36:36 +00:00
|
|
|
ready_pipe = -1
|
2011-11-12 07:08:12 +00:00
|
|
|
objects = { }
|
2011-09-25 16:39:03 +00:00
|
|
|
|
|
|
|
class NotSupported(dbus.exceptions.DBusException):
|
|
|
|
def __init__(self, msg):
|
|
|
|
dbus.exceptions.DBusException.__init__(self, msg, name="org.freedesktop.DBus.Error.NotSupported")
|
|
|
|
|
2011-09-25 20:59:43 +00:00
|
|
|
class InvalidArgs(dbus.exceptions.DBusException):
|
|
|
|
def __init__(self, msg):
|
|
|
|
dbus.exceptions.DBusException.__init__(self, msg, name="org.freedesktop.DBus.Error.InvalidArgs")
|
|
|
|
|
2011-11-12 07:08:12 +00:00
|
|
|
class IsLocked(dbus.exceptions.DBusException):
|
|
|
|
def __init__(self, msg):
|
|
|
|
dbus.exceptions.DBusException.__init__(self, msg, name="org.freedesktop.Secret.Error.IsLocked")
|
|
|
|
|
2012-01-31 19:20:31 +00:00
|
|
|
class NoSuchObject(dbus.exceptions.DBusException):
|
|
|
|
def __init__(self, msg):
|
|
|
|
dbus.exceptions.DBusException.__init__(self, msg, name="org.freedesktop.Secret.Error.NoSuchObject")
|
|
|
|
|
|
|
|
|
|
|
|
unique_identifier = 111
|
|
|
|
def next_identifier(prefix=''):
|
2011-09-25 16:39:03 +00:00
|
|
|
global unique_identifier
|
|
|
|
unique_identifier += 1
|
2012-01-30 13:14:39 +00:00
|
|
|
return "%s%d" % (prefix, unique_identifier)
|
2011-09-25 16:39:03 +00:00
|
|
|
|
2012-01-31 19:20:31 +00:00
|
|
|
def encode_identifier(value):
|
|
|
|
return "".join([(c.isalpha() or c.isdigit()) and c or "_%02x" % ord(c) \
|
|
|
|
for c in value.encode('utf-8')])
|
|
|
|
|
2011-11-12 07:08:12 +00:00
|
|
|
def hex_encode(string):
|
|
|
|
return "".join([hex(ord(c))[2:].zfill(2) for c in string])
|
|
|
|
|
2012-01-31 19:20:31 +00:00
|
|
|
def alias_path(name):
|
|
|
|
return "/org/freedesktop/secrets/aliases/%s" % name
|
2011-11-12 07:08:12 +00:00
|
|
|
|
2011-09-25 16:39:03 +00:00
|
|
|
class PlainAlgorithm():
|
|
|
|
def negotiate(self, service, sender, param):
|
2011-09-25 20:59:43 +00:00
|
|
|
if type (param) != dbus.String:
|
|
|
|
raise InvalidArgs("invalid argument passed to OpenSession")
|
2011-11-12 07:08:12 +00:00
|
|
|
session = SecretSession(service, sender, self, None)
|
2011-09-25 16:39:03 +00:00
|
|
|
return (dbus.String("", variant_level=1), session)
|
|
|
|
|
2011-11-12 07:08:12 +00:00
|
|
|
def encrypt(self, key, data):
|
|
|
|
return ("", data)
|
|
|
|
|
2012-01-30 13:14:39 +00:00
|
|
|
def decrypt(self, param, data):
|
|
|
|
if params == "":
|
|
|
|
raise InvalidArgs("invalid secret plain parameter")
|
|
|
|
return data
|
|
|
|
|
2011-11-12 07:08:12 +00:00
|
|
|
|
2011-09-25 16:39:03 +00:00
|
|
|
class AesAlgorithm():
|
|
|
|
def negotiate(self, service, sender, param):
|
2011-09-25 20:59:43 +00:00
|
|
|
if type (param) != dbus.ByteArray:
|
|
|
|
raise InvalidArgs("invalid argument passed to OpenSession")
|
2011-11-12 07:08:12 +00:00
|
|
|
privat, publi = dh.generate_pair()
|
2011-09-25 16:39:03 +00:00
|
|
|
peer = dh.bytes_to_number(param)
|
2011-11-12 07:08:12 +00:00
|
|
|
# print "mock publi: ", hex(publi)
|
|
|
|
# print " mock peer: ", hex(peer)
|
2011-09-25 16:39:03 +00:00
|
|
|
ikm = dh.derive_key(privat, peer)
|
2011-11-12 07:08:12 +00:00
|
|
|
# print " mock ikm: ", hex_encode(ikm)
|
2011-09-25 16:39:03 +00:00
|
|
|
key = hkdf.hkdf(ikm, 16)
|
2011-11-12 07:08:12 +00:00
|
|
|
# print " mock key: ", hex_encode(key)
|
|
|
|
session = SecretSession(service, sender, self, key)
|
2011-09-25 16:39:03 +00:00
|
|
|
return (dbus.ByteArray(dh.number_to_bytes(publi), variant_level=1), session)
|
|
|
|
|
2011-11-12 07:08:12 +00:00
|
|
|
def encrypt(self, key, data):
|
|
|
|
key = map(ord, key)
|
|
|
|
data = aes.append_PKCS7_padding(data)
|
|
|
|
keysize = len(key)
|
|
|
|
iv = [ord(i) for i in os.urandom(16)]
|
|
|
|
mode = aes.AESModeOfOperation.modeOfOperation["CBC"]
|
|
|
|
moo = aes.AESModeOfOperation()
|
|
|
|
(mode, length, ciph) = moo.encrypt(data, mode, key, keysize, iv)
|
|
|
|
return ("".join([chr(i) for i in iv]),
|
|
|
|
"".join([chr(i) for i in ciph]))
|
|
|
|
|
2012-01-30 13:14:39 +00:00
|
|
|
def decrypt(self, key, param, data):
|
|
|
|
key = map(ord, key)
|
|
|
|
keysize = len(key)
|
|
|
|
iv = map(ord, param[:16])
|
|
|
|
data = map(ord, data)
|
|
|
|
moo = aes.AESModeOfOperation()
|
|
|
|
mode = aes.AESModeOfOperation.modeOfOperation["CBC"]
|
|
|
|
decr = moo.decrypt(data, None, mode, key, keysize, iv)
|
|
|
|
return aes.strip_PKCS7_padding(decr)
|
|
|
|
|
2011-11-12 07:08:12 +00:00
|
|
|
|
2012-01-11 06:44:32 +00:00
|
|
|
class SecretPrompt(dbus.service.Object):
|
|
|
|
def __init__(self, service, sender, prompt_name=None, delay=0,
|
2012-01-30 15:07:55 +00:00
|
|
|
dismiss=False, action=None):
|
2012-01-11 06:44:32 +00:00
|
|
|
self.sender = sender
|
|
|
|
self.service = service
|
|
|
|
self.delay = 0
|
|
|
|
self.dismiss = False
|
2012-01-30 15:07:55 +00:00
|
|
|
self.result = dbus.String("", variant_level=1)
|
2012-01-11 06:44:32 +00:00
|
|
|
self.action = action
|
|
|
|
self.completed = False
|
|
|
|
if prompt_name:
|
|
|
|
self.path = "/org/freedesktop/secrets/prompts/%s" % prompt_name
|
|
|
|
else:
|
2012-01-30 13:14:39 +00:00
|
|
|
self.path = "/org/freedesktop/secrets/prompts/%s" % next_identifier('p')
|
2012-01-11 06:44:32 +00:00
|
|
|
dbus.service.Object.__init__(self, service.bus_name, self.path)
|
|
|
|
service.add_prompt(self)
|
|
|
|
assert self.path not in objects
|
|
|
|
objects[self.path] = self
|
|
|
|
|
|
|
|
def _complete(self):
|
|
|
|
if self.completed:
|
|
|
|
return
|
|
|
|
self.completed = True
|
|
|
|
self.Completed(self.dismiss, self.result)
|
|
|
|
self.remove_from_connection()
|
|
|
|
|
|
|
|
@dbus.service.method('org.freedesktop.Secret.Prompt')
|
|
|
|
def Prompt(self, window_id):
|
|
|
|
if self.action:
|
2012-01-30 15:07:55 +00:00
|
|
|
self.result = self.action()
|
2012-01-11 06:44:32 +00:00
|
|
|
gobject.timeout_add(self.delay * 1000, self._complete)
|
|
|
|
|
|
|
|
@dbus.service.method('org.freedesktop.Secret.Prompt')
|
|
|
|
def Dismiss(self):
|
|
|
|
self._complete()
|
|
|
|
|
|
|
|
@dbus.service.signal(dbus_interface='org.freedesktop.Secret.Prompt', signature='bv')
|
|
|
|
def Completed(self, dismiss, result):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2011-09-25 16:39:03 +00:00
|
|
|
class SecretSession(dbus.service.Object):
|
2011-11-12 07:08:12 +00:00
|
|
|
def __init__(self, service, sender, algorithm, key):
|
2011-09-25 16:39:03 +00:00
|
|
|
self.sender = sender
|
|
|
|
self.service = service
|
2011-11-12 07:08:12 +00:00
|
|
|
self.algorithm = algorithm
|
2011-09-25 16:39:03 +00:00
|
|
|
self.key = key
|
2012-01-30 13:14:39 +00:00
|
|
|
self.path = "/org/freedesktop/secrets/sessions/%s" % next_identifier('s')
|
2011-09-25 16:39:03 +00:00
|
|
|
dbus.service.Object.__init__(self, service.bus_name, self.path)
|
|
|
|
service.add_session(self)
|
2011-11-12 07:08:12 +00:00
|
|
|
objects[self.path] = self
|
|
|
|
|
|
|
|
def encode_secret(self, secret, content_type):
|
|
|
|
(params, data) = self.algorithm.encrypt(self.key, secret)
|
|
|
|
# print " mock iv: ", hex_encode(params)
|
|
|
|
# print " mock ciph: ", hex_encode(data)
|
2012-01-23 16:20:18 +00:00
|
|
|
return dbus.Struct((dbus.ObjectPath(self.path), dbus.ByteArray(params),
|
|
|
|
dbus.ByteArray(data), dbus.String(content_type)),
|
|
|
|
signature="oayays")
|
2011-09-25 16:39:03 +00:00
|
|
|
|
2012-01-30 13:14:39 +00:00
|
|
|
def decode_secret(self, value):
|
|
|
|
plain = self.algorithm.decrypt(self.key, value[1], value[2])
|
|
|
|
return (plain, value[3])
|
|
|
|
|
2011-09-25 16:39:03 +00:00
|
|
|
@dbus.service.method('org.freedesktop.Secret.Session')
|
|
|
|
def Close(self):
|
|
|
|
self.remove_from_connection()
|
|
|
|
self.service.remove_session(self)
|
|
|
|
|
2011-11-05 20:50:01 +00:00
|
|
|
|
|
|
|
class SecretItem(dbus.service.Object):
|
2012-01-31 19:20:31 +00:00
|
|
|
SUPPORTS_MULTIPLE_OBJECT_PATHS = True
|
|
|
|
|
|
|
|
def __init__(self, collection, identifier=None, label="Item", attributes={ },
|
|
|
|
secret="", confirm=False, content_type="text/plain", type=None):
|
|
|
|
if identifier is None:
|
|
|
|
identifier = next_identifier()
|
|
|
|
identifier = encode_identifier(identifier)
|
2011-11-05 20:50:01 +00:00
|
|
|
self.collection = collection
|
|
|
|
self.identifier = identifier
|
2012-01-30 16:49:22 +00:00
|
|
|
self.label = label or "Unnamed item"
|
2011-11-06 21:46:30 +00:00
|
|
|
self.secret = secret
|
2012-01-31 19:20:31 +00:00
|
|
|
self.type = type or "org.freedesktop.Secret.Generic"
|
2011-11-05 20:50:01 +00:00
|
|
|
self.attributes = attributes
|
2011-11-12 07:08:12 +00:00
|
|
|
self.content_type = content_type
|
|
|
|
self.path = "%s/%s" % (collection.path, identifier)
|
2012-01-11 06:44:32 +00:00
|
|
|
self.confirm = confirm
|
2012-01-23 16:20:18 +00:00
|
|
|
self.created = self.modified = time.time()
|
2011-11-05 20:50:01 +00:00
|
|
|
dbus.service.Object.__init__(self, collection.service.bus_name, self.path)
|
2012-01-31 19:20:31 +00:00
|
|
|
self.collection.add_item(self)
|
2011-11-12 07:08:12 +00:00
|
|
|
objects[self.path] = self
|
2011-11-05 20:50:01 +00:00
|
|
|
|
2012-01-31 19:20:31 +00:00
|
|
|
def add_alias(self, name):
|
|
|
|
path = "%s/%s" % (alias_path(name), self.identifier)
|
|
|
|
objects[path] = self
|
|
|
|
self.add_to_connection(self.connection, path)
|
|
|
|
|
|
|
|
def remove_alias(self, name):
|
|
|
|
path = "%s/%s" % (alias_path(name), self.identifier)
|
|
|
|
del objects[path]
|
|
|
|
self.remove_from_connection(self.connection, path)
|
|
|
|
|
2011-11-05 20:50:01 +00:00
|
|
|
def match_attributes(self, attributes):
|
|
|
|
for (key, value) in attributes.items():
|
|
|
|
if not self.attributes.get(key) == value:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2012-01-26 13:34:37 +00:00
|
|
|
def get_locked(self):
|
|
|
|
return self.collection.locked
|
|
|
|
|
|
|
|
def perform_xlock(self, lock):
|
|
|
|
return self.collection.perform_xlock(lock)
|
|
|
|
|
2012-01-11 06:44:32 +00:00
|
|
|
def perform_delete(self):
|
2012-01-31 19:20:31 +00:00
|
|
|
self.collection.remove_item(self)
|
2012-01-11 06:44:32 +00:00
|
|
|
del objects[self.path]
|
2012-01-23 16:20:18 +00:00
|
|
|
self.remove_from_connection()
|
2012-01-11 06:44:32 +00:00
|
|
|
|
2011-11-12 07:08:12 +00:00
|
|
|
@dbus.service.method('org.freedesktop.Secret.Item', sender_keyword='sender')
|
|
|
|
def GetSecret(self, session_path, sender=None):
|
|
|
|
session = objects.get(session_path, None)
|
|
|
|
if not session or session.sender != sender:
|
2012-01-31 19:20:31 +00:00
|
|
|
raise InvalidArgs("session invalid: %s" % session_path)
|
2012-01-26 13:34:37 +00:00
|
|
|
if self.get_locked():
|
2011-11-12 07:08:12 +00:00
|
|
|
raise IsLocked("secret is locked: %s" % self.path)
|
|
|
|
return session.encode_secret(self.secret, self.content_type)
|
|
|
|
|
2012-01-30 16:49:22 +00:00
|
|
|
@dbus.service.method('org.freedesktop.Secret.Item', sender_keyword='sender', byte_arrays=True)
|
|
|
|
def SetSecret(self, secret, sender=None):
|
|
|
|
session = objects.get(secret[0], None)
|
|
|
|
if not session or session.sender != sender:
|
2012-01-31 19:20:31 +00:00
|
|
|
raise InvalidArgs("session invalid: %s" % secret[0])
|
2012-01-30 16:49:22 +00:00
|
|
|
if self.get_locked():
|
|
|
|
raise IsLocked("secret is locked: %s" % self.path)
|
|
|
|
(self.secret, self.content_type) = session.decode_secret(secret)
|
|
|
|
|
2012-01-11 06:44:32 +00:00
|
|
|
@dbus.service.method('org.freedesktop.Secret.Item', sender_keyword='sender')
|
|
|
|
def Delete(self, sender=None):
|
2012-01-30 15:07:55 +00:00
|
|
|
item = self
|
|
|
|
def prompt_callback():
|
|
|
|
item.perform_delete()
|
|
|
|
return dbus.String("", variant_level=1)
|
2012-01-11 06:44:32 +00:00
|
|
|
if self.confirm:
|
|
|
|
prompt = SecretPrompt(self.collection.service, sender,
|
2012-01-30 15:07:55 +00:00
|
|
|
dismiss=False, action=prompt_callback)
|
2012-01-11 06:44:32 +00:00
|
|
|
return dbus.ObjectPath(prompt.path)
|
|
|
|
else:
|
|
|
|
self.perform_delete()
|
|
|
|
return dbus.ObjectPath("/")
|
|
|
|
|
2012-01-23 16:20:18 +00:00
|
|
|
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature='ss', out_signature='v')
|
|
|
|
def Get(self, interface_name, property_name):
|
|
|
|
return self.GetAll(interface_name)[property_name]
|
|
|
|
|
|
|
|
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature='s', out_signature='a{sv}')
|
|
|
|
def GetAll(self, interface_name):
|
|
|
|
if interface_name == 'org.freedesktop.Secret.Item':
|
|
|
|
return {
|
2012-01-26 13:34:37 +00:00
|
|
|
'Locked': self.get_locked(),
|
2012-01-30 16:49:22 +00:00
|
|
|
'Attributes': dbus.Dictionary(self.attributes, signature='ss', variant_level=1),
|
2012-01-23 16:20:18 +00:00
|
|
|
'Label': self.label,
|
|
|
|
'Created': dbus.UInt64(self.created),
|
2012-01-30 16:49:22 +00:00
|
|
|
'Modified': dbus.UInt64(self.modified),
|
2012-03-18 18:04:44 +00:00
|
|
|
|
|
|
|
# For compatibility with libgnome-keyring, not part of spec
|
2012-01-31 19:20:31 +00:00
|
|
|
'Type': self.type
|
2012-01-23 16:20:18 +00:00
|
|
|
}
|
|
|
|
else:
|
|
|
|
raise InvalidArgs('Unknown %s interface' % interface_name)
|
|
|
|
|
|
|
|
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature='ssv')
|
|
|
|
def Set(self, interface_name, property_name, new_value):
|
|
|
|
if interface_name != 'org.freedesktop.Secret.Item':
|
|
|
|
raise InvalidArgs('Unknown %s interface' % interface_name)
|
|
|
|
if property_name == "Label":
|
|
|
|
self.label = str(new_value)
|
|
|
|
elif property_name == "Attributes":
|
|
|
|
self.attributes = dict(new_value)
|
2012-03-18 18:04:44 +00:00
|
|
|
# For compatibility with libgnome-keyring, not part of spec
|
2012-01-31 19:20:31 +00:00
|
|
|
elif property_name == "Type":
|
|
|
|
self.type = str(new_value)
|
2012-01-23 16:20:18 +00:00
|
|
|
else:
|
2012-01-31 19:20:31 +00:00
|
|
|
raise InvalidArgs('Not writable %s property' % property_name)
|
2012-01-23 16:20:18 +00:00
|
|
|
self.PropertiesChanged(interface_name, { property_name: new_value }, [])
|
|
|
|
|
|
|
|
@dbus.service.signal(dbus.PROPERTIES_IFACE, signature='sa{sv}as')
|
|
|
|
def PropertiesChanged(self, interface_name, changed_properties, invalidated_properties):
|
|
|
|
self.modified = time.time()
|
|
|
|
|
2011-11-05 20:50:01 +00:00
|
|
|
|
|
|
|
class SecretCollection(dbus.service.Object):
|
2012-01-31 19:20:31 +00:00
|
|
|
SUPPORTS_MULTIPLE_OBJECT_PATHS = True
|
|
|
|
|
|
|
|
def __init__(self, service, identifier=None, label="Collection", locked=False,
|
|
|
|
confirm=False, master=None):
|
|
|
|
if identifier is None:
|
|
|
|
identifier = label
|
|
|
|
identifier = encode_identifier(identifier)
|
2011-11-05 20:50:01 +00:00
|
|
|
self.service = service
|
|
|
|
self.identifier = identifier
|
2012-01-30 16:49:22 +00:00
|
|
|
self.label = label or "Unnamed collection"
|
2011-11-05 20:50:01 +00:00
|
|
|
self.locked = locked
|
|
|
|
self.items = { }
|
2012-01-23 16:20:18 +00:00
|
|
|
self.confirm = confirm
|
2012-01-31 19:20:31 +00:00
|
|
|
self.master = None
|
2012-01-23 16:20:18 +00:00
|
|
|
self.created = self.modified = time.time()
|
2012-01-31 19:20:31 +00:00
|
|
|
self.aliased = set()
|
2011-11-12 07:08:12 +00:00
|
|
|
self.path = "%s%s" % (COLLECTION_PREFIX, identifier)
|
2011-11-05 20:50:01 +00:00
|
|
|
dbus.service.Object.__init__(self, service.bus_name, self.path)
|
2012-01-31 19:20:31 +00:00
|
|
|
self.service.add_collection(self)
|
2011-11-12 07:08:12 +00:00
|
|
|
objects[self.path] = self
|
2011-11-05 20:50:01 +00:00
|
|
|
|
2012-01-31 19:20:31 +00:00
|
|
|
def add_item(self, item):
|
|
|
|
self.items[item.path] = item
|
|
|
|
for alias in self.aliased:
|
|
|
|
item.add_alias(alias)
|
|
|
|
|
|
|
|
def remove_item(self, item):
|
|
|
|
for alias in self.aliased:
|
|
|
|
item.remove_alias(alias)
|
|
|
|
del self.items[item.path]
|
|
|
|
|
|
|
|
def add_alias(self, name):
|
|
|
|
if name in self.aliased:
|
|
|
|
return
|
|
|
|
self.aliased.add(name)
|
|
|
|
for item in self.items.values():
|
|
|
|
item.add_alias(name)
|
|
|
|
path = alias_path(name)
|
|
|
|
objects[path] = self
|
|
|
|
self.add_to_connection(self.connection, path)
|
|
|
|
|
|
|
|
def remove_alias(self, name):
|
|
|
|
if name not in self.aliased:
|
|
|
|
return
|
|
|
|
path = alias_path(name)
|
|
|
|
self.aliased.remove(name)
|
|
|
|
del objects[path]
|
|
|
|
self.remove_from_connection(self.connection, path)
|
|
|
|
for item in self.items.values():
|
|
|
|
item.remove_alias(name)
|
|
|
|
|
2011-11-05 20:50:01 +00:00
|
|
|
def search_items(self, attributes):
|
|
|
|
results = []
|
|
|
|
for item in self.items.values():
|
|
|
|
if item.match_attributes(attributes):
|
|
|
|
results.append(item)
|
|
|
|
return results
|
|
|
|
|
2012-01-26 13:34:37 +00:00
|
|
|
def get_locked(self):
|
|
|
|
return self.locked
|
|
|
|
|
|
|
|
def perform_xlock(self, lock):
|
|
|
|
self.locked = lock
|
|
|
|
for item in self.items.values():
|
|
|
|
self.PropertiesChanged('org.freedesktop.Secret.Item', { "Locked" : lock }, [])
|
|
|
|
self.PropertiesChanged('org.freedesktop.Secret.Collection', { "Locked" : lock }, [])
|
|
|
|
|
2012-01-23 16:20:18 +00:00
|
|
|
def perform_delete(self):
|
|
|
|
for item in self.items.values():
|
|
|
|
item.perform_delete()
|
|
|
|
del objects[self.path]
|
2012-01-31 19:20:31 +00:00
|
|
|
self.service.remove_collection(self)
|
|
|
|
for alias in list(self.aliased):
|
|
|
|
self.remove_alias(alias)
|
2012-01-23 16:20:18 +00:00
|
|
|
self.remove_from_connection()
|
|
|
|
|
2012-01-30 13:14:39 +00:00
|
|
|
@dbus.service.method('org.freedesktop.Secret.Collection', byte_arrays=True, sender_keyword='sender')
|
|
|
|
def CreateItem(self, properties, value, replace, sender=None):
|
|
|
|
session_path = value[0]
|
|
|
|
session = objects.get(session_path, None)
|
|
|
|
if not session or session.sender != sender:
|
|
|
|
raise InvalidArgs("session invalid: %s" % session_path)
|
|
|
|
|
2012-03-18 18:04:44 +00:00
|
|
|
attributes = properties.get("org.freedesktop.Secret.Item.Attributes", { })
|
2012-01-30 13:14:39 +00:00
|
|
|
label = properties.get("org.freedesktop.Secret.Item.Label", None)
|
|
|
|
(secret, content_type) = session.decode_secret(value)
|
|
|
|
item = None
|
|
|
|
|
2012-03-18 18:04:44 +00:00
|
|
|
# This is done for compatibility with libgnome-keyring, not part of spec
|
|
|
|
type = properties.get("org.freedesktop.Secret.Item.Type", None)
|
|
|
|
|
2012-01-31 19:20:31 +00:00
|
|
|
if replace:
|
2012-01-30 13:14:39 +00:00
|
|
|
items = self.search_items(attributes)
|
|
|
|
if items:
|
|
|
|
item = items[0]
|
|
|
|
if item is None:
|
2012-01-31 19:20:31 +00:00
|
|
|
item = SecretItem(self, next_identifier(), label, attributes, type=type,
|
2012-01-30 13:14:39 +00:00
|
|
|
secret=secret, confirm=False, content_type=content_type)
|
|
|
|
else:
|
|
|
|
item.label = label
|
2012-01-31 19:20:31 +00:00
|
|
|
item.type = type
|
2012-01-30 13:14:39 +00:00
|
|
|
item.secret = secret
|
|
|
|
item.attributes = attributes
|
|
|
|
item.content_type = content_type
|
|
|
|
return (dbus.ObjectPath(item.path), dbus.ObjectPath("/"))
|
|
|
|
|
2012-01-23 16:20:18 +00:00
|
|
|
@dbus.service.method('org.freedesktop.Secret.Collection', sender_keyword='sender')
|
|
|
|
def Delete(self, sender=None):
|
2012-01-30 15:07:55 +00:00
|
|
|
collection = self
|
|
|
|
def prompt_callback():
|
|
|
|
collection.perform_delete()
|
|
|
|
return dbus.String("", variant_level=1)
|
2012-01-23 16:20:18 +00:00
|
|
|
if self.confirm:
|
2012-01-31 19:20:31 +00:00
|
|
|
prompt = SecretPrompt(self.service, sender, dismiss=False,
|
|
|
|
action=prompt_callback)
|
2012-01-23 16:20:18 +00:00
|
|
|
return dbus.ObjectPath(prompt.path)
|
|
|
|
else:
|
|
|
|
self.perform_delete()
|
|
|
|
return dbus.ObjectPath("/")
|
|
|
|
|
|
|
|
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature='ss', out_signature='v')
|
|
|
|
def Get(self, interface_name, property_name):
|
|
|
|
return self.GetAll(interface_name)[property_name]
|
|
|
|
|
|
|
|
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature='s', out_signature='a{sv}')
|
|
|
|
def GetAll(self, interface_name):
|
|
|
|
if interface_name == 'org.freedesktop.Secret.Collection':
|
|
|
|
return {
|
2012-01-26 13:34:37 +00:00
|
|
|
'Locked': self.get_locked(),
|
2012-01-23 16:20:18 +00:00
|
|
|
'Label': self.label,
|
|
|
|
'Created': dbus.UInt64(self.created),
|
|
|
|
'Modified': dbus.UInt64(self.modified),
|
2012-01-30 16:49:22 +00:00
|
|
|
'Items': dbus.Array([dbus.ObjectPath(i.path) for i in self.items.values()], signature='o', variant_level=1)
|
2012-01-23 16:20:18 +00:00
|
|
|
}
|
|
|
|
else:
|
|
|
|
raise InvalidArgs('Unknown %s interface' % interface_name)
|
|
|
|
|
|
|
|
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature='ssv')
|
|
|
|
def Set(self, interface_name, property_name, new_value):
|
|
|
|
if interface_name != 'org.freedesktop.Secret.Collection':
|
|
|
|
raise InvalidArgs('Unknown %s interface' % interface_name)
|
|
|
|
if property_name == "Label":
|
|
|
|
self.label = str(new_value)
|
|
|
|
else:
|
2012-01-26 13:34:37 +00:00
|
|
|
raise InvalidArgs('Not a writable property %s' % property_name)
|
2012-01-23 16:20:18 +00:00
|
|
|
self.PropertiesChanged(interface_name, { property_name: new_value }, [])
|
|
|
|
|
|
|
|
@dbus.service.signal(dbus.PROPERTIES_IFACE, signature='sa{sv}as')
|
|
|
|
def PropertiesChanged(self, interface_name, changed_properties, invalidated_properties):
|
|
|
|
self.modified = time.time()
|
|
|
|
|
2011-11-05 20:50:01 +00:00
|
|
|
|
2011-09-25 16:39:03 +00:00
|
|
|
class SecretService(dbus.service.Object):
|
|
|
|
|
|
|
|
algorithms = {
|
|
|
|
'plain': PlainAlgorithm(),
|
|
|
|
"dh-ietf1024-sha256-aes128-cbc-pkcs7": AesAlgorithm(),
|
|
|
|
}
|
|
|
|
|
2011-09-25 20:59:43 +00:00
|
|
|
def __init__(self, name=None):
|
|
|
|
if name == None:
|
|
|
|
name = bus_name
|
|
|
|
bus = dbus.SessionBus()
|
|
|
|
self.bus_name = dbus.service.BusName(name, allow_replacement=True, replace_existing=True)
|
2011-09-25 16:39:03 +00:00
|
|
|
dbus.service.Object.__init__(self, self.bus_name, '/org/freedesktop/secrets')
|
|
|
|
self.sessions = { }
|
2012-01-11 06:44:32 +00:00
|
|
|
self.prompts = { }
|
2011-11-05 20:50:01 +00:00
|
|
|
self.collections = { }
|
2012-01-31 19:20:31 +00:00
|
|
|
self.aliases = { }
|
|
|
|
self.aliased = { }
|
2011-09-25 16:39:03 +00:00
|
|
|
|
|
|
|
def on_name_owner_changed(owned, old_owner, new_owner):
|
|
|
|
if not new_owner:
|
|
|
|
for session in list(self.sessions.get(old_owner, [])):
|
|
|
|
session.Close()
|
|
|
|
|
|
|
|
bus.add_signal_receiver(on_name_owner_changed,
|
|
|
|
'NameOwnerChanged',
|
|
|
|
'org.freedesktop.DBus')
|
|
|
|
|
2011-11-12 07:08:12 +00:00
|
|
|
def add_standard_objects(self):
|
2012-01-26 13:34:37 +00:00
|
|
|
collection = SecretCollection(self, "english", label="Collection One", locked=False)
|
2012-03-18 18:04:44 +00:00
|
|
|
SecretItem(collection, "1", label="Item One", secret="111",
|
|
|
|
attributes={ "number": "1", "string": "one", "even": "false", "xdg:schema": "org.mock.Schema" })
|
|
|
|
SecretItem(collection, "2", label="Item Two", secret="222",
|
|
|
|
attributes={ "number": "2", "string": "two", "even": "true", "xdg:schema": "org.mock.Schema" })
|
|
|
|
SecretItem(collection, "3", label="Item Three", secret="333",
|
|
|
|
attributes={ "number": "3", "string": "three", "even": "false", "xdg:schema": "org.mock.Schema" })
|
2012-01-31 19:20:31 +00:00
|
|
|
|
|
|
|
self.set_alias('default', collection)
|
2012-01-26 13:34:37 +00:00
|
|
|
|
|
|
|
collection = SecretCollection(self, "spanish", locked=True)
|
2012-03-18 18:04:44 +00:00
|
|
|
SecretItem(collection, "10", secret="111",
|
|
|
|
attributes={ "number": "1", "string": "uno", "even": "false", "xdg:schema": "org.mock.Schema" })
|
|
|
|
SecretItem(collection, "20", secret="222",
|
|
|
|
attributes={ "number": "2", "string": "dos", "even": "true", "xdg:schema": "org.mock.Schema" })
|
|
|
|
SecretItem(collection, "30", secret="3333",
|
|
|
|
attributes={ "number": "3", "string": "tres", "even": "false", "xdg:schema": "org.mock.Schema" })
|
|
|
|
|
|
|
|
collection = SecretCollection(self, "german", locked=True)
|
|
|
|
SecretItem(collection, "300", secret="333",
|
|
|
|
attributes={ "number": "3", "string": "drei", "prime": "true", "xdg:schema": "org.mock.Primes" })
|
|
|
|
SecretItem(collection, "400", secret="444",
|
|
|
|
attributes={ "number": "4", "string": "vier", "prime": "false", "xdg:schema": "org.mock.Primes" })
|
|
|
|
SecretItem(collection, "500", secret="555",
|
|
|
|
attributes={ "number": "5", "string": "fuenf", "prime": "true", "xdg:schema": "org.mock.Primes" })
|
|
|
|
SecretItem(collection, "600", secret="666",
|
|
|
|
attributes={ "number": "6", "string": "sechs", "prime": "false", "xdg:schema": "org.mock.Primes" })
|
2012-01-31 19:20:31 +00:00
|
|
|
|
2012-01-26 13:34:37 +00:00
|
|
|
collection = SecretCollection(self, "empty", locked=False)
|
2012-01-31 19:20:31 +00:00
|
|
|
collection = SecretCollection(self, "session", label="Session Keyring", locked=False)
|
|
|
|
|
2012-03-18 18:04:44 +00:00
|
|
|
self.set_alias('session', collection)
|
|
|
|
|
2011-09-25 16:39:03 +00:00
|
|
|
def listen(self):
|
2012-01-23 08:36:36 +00:00
|
|
|
global ready_pipe
|
2011-09-25 16:39:03 +00:00
|
|
|
loop = gobject.MainLoop()
|
2012-01-23 08:36:36 +00:00
|
|
|
if ready_pipe >= 0:
|
|
|
|
os.write(ready_pipe, "GO")
|
|
|
|
os.close(ready_pipe)
|
|
|
|
ready_pipe = -1
|
2011-09-25 16:39:03 +00:00
|
|
|
loop.run()
|
|
|
|
|
|
|
|
def add_session(self, session):
|
|
|
|
if session.sender not in self.sessions:
|
|
|
|
self.sessions[session.sender] = []
|
|
|
|
self.sessions[session.sender].append(session)
|
|
|
|
|
|
|
|
def remove_session(self, session):
|
|
|
|
self.sessions[session.sender].remove(session)
|
2011-11-05 20:50:01 +00:00
|
|
|
|
2012-01-31 19:20:31 +00:00
|
|
|
def add_collection(self, collection):
|
|
|
|
self.collections[collection.path] = collection
|
|
|
|
|
|
|
|
def remove_collection(self, collection):
|
|
|
|
for alias in list(collection.aliased):
|
|
|
|
self.remove_alias(alias)
|
|
|
|
del self.collections[collection.path]
|
|
|
|
|
|
|
|
def set_alias(self, name, collection):
|
|
|
|
self.remove_alias(name)
|
2012-06-23 12:09:50 +00:00
|
|
|
if collection:
|
|
|
|
collection.add_alias(name)
|
|
|
|
self.aliases[name] = collection
|
|
|
|
elif name in self.aliases:
|
|
|
|
del self.aliases[name]
|
2012-01-31 19:20:31 +00:00
|
|
|
|
|
|
|
def remove_alias(self, name):
|
|
|
|
if name in self.aliases:
|
|
|
|
collection = self.aliases[name]
|
|
|
|
collection.remove_alias(name)
|
|
|
|
del self.aliases[name]
|
|
|
|
|
2012-01-11 06:44:32 +00:00
|
|
|
def add_prompt(self, prompt):
|
|
|
|
if prompt.sender not in self.prompts:
|
|
|
|
self.prompts[prompt.sender] = []
|
|
|
|
self.prompts[prompt.sender].append(prompt)
|
|
|
|
|
|
|
|
def remove_prompt (self, prompt):
|
|
|
|
self.prompts[prompt.sender].remove(prompt)
|
|
|
|
|
2011-11-12 07:08:12 +00:00
|
|
|
def find_item(self, object):
|
2012-01-31 19:20:31 +00:00
|
|
|
parts = object.rsplit("/", 1)
|
|
|
|
if len(parts) == 2 and parts[0] in self.collections:
|
|
|
|
return self.collections[parts[0]].get(parts[1], None)
|
2011-11-12 07:08:12 +00:00
|
|
|
return None
|
|
|
|
|
2012-01-26 13:34:37 +00:00
|
|
|
@dbus.service.method('org.freedesktop.Secret.Service', sender_keyword='sender')
|
|
|
|
def Lock(self, paths, lock=True, sender=None):
|
|
|
|
locked = []
|
|
|
|
prompts = []
|
|
|
|
for path in paths:
|
|
|
|
if path not in objects:
|
|
|
|
continue
|
|
|
|
object = objects[path]
|
|
|
|
if object.get_locked() == lock:
|
|
|
|
locked.append(path)
|
|
|
|
elif not object.confirm:
|
|
|
|
object.perform_xlock(lock)
|
|
|
|
locked.append(path)
|
|
|
|
else:
|
|
|
|
prompts.append(object)
|
|
|
|
def prompt_callback():
|
|
|
|
for object in prompts:
|
|
|
|
object.perform_xlock(lock)
|
2012-01-30 15:07:55 +00:00
|
|
|
return dbus.Array([o.path for o in prompts], signature='o')
|
2012-01-26 13:34:37 +00:00
|
|
|
locked = dbus.Array(locked, signature='o')
|
|
|
|
if prompts:
|
2012-01-30 15:07:55 +00:00
|
|
|
prompt = SecretPrompt(self, sender, dismiss=False, action=prompt_callback)
|
2012-01-26 13:34:37 +00:00
|
|
|
return (locked, dbus.ObjectPath(prompt.path))
|
|
|
|
else:
|
|
|
|
return (locked, dbus.ObjectPath("/"))
|
|
|
|
|
|
|
|
@dbus.service.method('org.freedesktop.Secret.Service', sender_keyword='sender')
|
|
|
|
def Unlock(self, paths, sender=None):
|
|
|
|
return self.Lock(paths, lock=False, sender=sender)
|
|
|
|
|
2011-09-25 16:39:03 +00:00
|
|
|
@dbus.service.method('org.freedesktop.Secret.Service', byte_arrays=True, sender_keyword='sender')
|
|
|
|
def OpenSession(self, algorithm, param, sender=None):
|
|
|
|
assert type(algorithm) == dbus.String
|
|
|
|
|
|
|
|
if algorithm not in self.algorithms:
|
|
|
|
raise NotSupported("algorithm %s is not supported" % algorithm)
|
|
|
|
|
|
|
|
return self.algorithms[algorithm].negotiate(self, sender, param)
|
|
|
|
|
2012-01-30 15:07:55 +00:00
|
|
|
@dbus.service.method('org.freedesktop.Secret.Service', sender_keyword='sender')
|
|
|
|
def CreateCollection(self, properties, alias, sender=None):
|
2012-01-30 16:49:22 +00:00
|
|
|
label = properties.get("org.freedesktop.Secret.Collection.Label", None)
|
2012-01-30 15:07:55 +00:00
|
|
|
service = self
|
|
|
|
def prompt_callback():
|
2012-01-31 19:20:31 +00:00
|
|
|
collection = SecretCollection(service, None, label, locked=False, confirm=True)
|
2012-01-30 15:07:55 +00:00
|
|
|
return dbus.ObjectPath(collection.path, variant_level=1)
|
|
|
|
prompt = SecretPrompt(self, sender, dismiss=False, action=prompt_callback)
|
|
|
|
return (dbus.ObjectPath("/"), dbus.ObjectPath(prompt.path))
|
|
|
|
|
2011-11-05 20:50:01 +00:00
|
|
|
@dbus.service.method('org.freedesktop.Secret.Service')
|
|
|
|
def SearchItems(self, attributes):
|
|
|
|
locked = [ ]
|
|
|
|
unlocked = [ ]
|
|
|
|
items = [ ]
|
|
|
|
for collection in self.collections.values():
|
|
|
|
items = collection.search_items(attributes)
|
2012-01-26 13:34:37 +00:00
|
|
|
if collection.get_locked():
|
2012-01-31 19:20:31 +00:00
|
|
|
locked.extend([item.path for item in items])
|
2011-11-05 20:50:01 +00:00
|
|
|
else:
|
2012-01-31 19:20:31 +00:00
|
|
|
unlocked.extend([item.path for item in items])
|
2011-11-05 20:50:01 +00:00
|
|
|
return (dbus.Array(unlocked, "o"), dbus.Array(locked, "o"))
|
|
|
|
|
2011-11-12 07:08:12 +00:00
|
|
|
@dbus.service.method('org.freedesktop.Secret.Service', sender_keyword='sender')
|
|
|
|
def GetSecrets(self, item_paths, session_path, sender=None):
|
|
|
|
session = objects.get(session_path, None)
|
|
|
|
if not session or session.sender != sender:
|
2012-01-31 19:20:31 +00:00
|
|
|
raise InvalidArgs("session invalid: %s" % session_path)
|
2011-11-12 07:08:12 +00:00
|
|
|
results = dbus.Dictionary(signature="o(oayays)")
|
|
|
|
for item_path in item_paths:
|
|
|
|
item = objects.get(item_path, None)
|
2012-01-26 13:34:37 +00:00
|
|
|
if item and not item.get_locked():
|
2011-11-12 07:08:12 +00:00
|
|
|
results[item_path] = item.GetSecret(session_path, sender)
|
|
|
|
return results
|
2011-11-05 20:50:01 +00:00
|
|
|
|
2012-01-31 19:20:31 +00:00
|
|
|
@dbus.service.method('org.freedesktop.Secret.Service')
|
|
|
|
def ReadAlias(self, name):
|
|
|
|
if name not in self.aliases:
|
|
|
|
return dbus.ObjectPath("/")
|
|
|
|
return dbus.ObjectPath(self.aliases[name].path)
|
|
|
|
|
|
|
|
@dbus.service.method('org.freedesktop.Secret.Service')
|
|
|
|
def SetAlias(self, name, collection):
|
2012-06-23 12:09:50 +00:00
|
|
|
if collection == dbus.ObjectPath("/"):
|
|
|
|
self.set_alias(name, None)
|
|
|
|
else:
|
|
|
|
if collection not in self.collections:
|
|
|
|
raise NoSuchObject("no such Collection")
|
|
|
|
self.set_alias(name, self.collections[collection])
|
2012-01-31 19:20:31 +00:00
|
|
|
|
2012-01-26 13:34:37 +00:00
|
|
|
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature='ss', out_signature='v')
|
|
|
|
def Get(self, interface_name, property_name):
|
|
|
|
return self.GetAll(interface_name)[property_name]
|
|
|
|
|
|
|
|
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature='s', out_signature='a{sv}')
|
|
|
|
def GetAll(self, interface_name):
|
|
|
|
if interface_name == 'org.freedesktop.Secret.Service':
|
|
|
|
return {
|
2012-01-30 16:49:22 +00:00
|
|
|
'Collections': dbus.Array([dbus.ObjectPath(c.path) for c in self.collections.values()], signature='o', variant_level=1)
|
2012-01-26 13:34:37 +00:00
|
|
|
}
|
|
|
|
else:
|
|
|
|
raise InvalidArgs('Unknown %s interface' % interface_name)
|
|
|
|
|
|
|
|
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature='ssv')
|
|
|
|
def Set(self, interface_name, property_name, new_value):
|
|
|
|
if interface_name != 'org.freedesktop.Secret.Collection':
|
|
|
|
raise InvalidArgs('Unknown %s interface' % interface_name)
|
|
|
|
raise InvalidArgs('Not a writable property %s' % property_name)
|
|
|
|
|
2012-01-11 06:44:32 +00:00
|
|
|
|
2011-09-25 16:39:03 +00:00
|
|
|
def parse_options(args):
|
2012-01-23 08:36:36 +00:00
|
|
|
global bus_name, ready_pipe
|
2011-09-25 16:39:03 +00:00
|
|
|
try:
|
2012-01-23 08:36:36 +00:00
|
|
|
opts, args = getopt.getopt(args, "nr", ["name=", "ready="])
|
2011-09-25 16:39:03 +00:00
|
|
|
except getopt.GetoptError, err:
|
|
|
|
print str(err)
|
|
|
|
sys.exit(2)
|
|
|
|
for o, a in opts:
|
2012-01-23 08:36:36 +00:00
|
|
|
if o in ("-n", "--name"):
|
2011-09-25 16:39:03 +00:00
|
|
|
bus_name = a
|
2012-01-23 08:36:36 +00:00
|
|
|
elif o in ("-r", "--ready"):
|
|
|
|
ready_pipe = int(a)
|
2011-09-25 16:39:03 +00:00
|
|
|
else:
|
|
|
|
assert False, "unhandled option"
|
|
|
|
return args
|
|
|
|
|
2011-09-25 20:59:43 +00:00
|
|
|
parse_options(sys.argv[1:])
|