github
Advanced Search
  • Home
  • Pricing and Signup
  • Explore GitHub
  • Blog
  • Login

acdha / pymacadmin

  • Admin
  • Watch Unwatch
  • Fork
  • Your Fork
  • Pull Request
  • Download Source
    • 7
    • 2
  • Source
  • Commits
  • Network (2)
  • Issues (0)
  • Downloads (0)
  • Wiki (1)
  • Graphs
  • Tree: 649dae5

click here to add a description

click here to add a homepage

  • Branches (3)
    • crankd_refactor
    • keychain_overhaul
    • master
  • Tags (0)
Sending Request…
Enable Donations

Pledgie Donations

Once activated, we'll place the following badge in your repository's detail box:
Pledgie_example
This service is courtesy of Pledgie.

Python tools for Mac system administration — Read more

  cancel

http://pymacadmin.googlecode.com/

  cancel
  • Private
  • Read-Only
  • HTTP Read-Only

This URL has Read+Write access

Keychain cleanup 
acdha (author)
Sat Mar 21 13:55:16 -0700 2009
commit  649dae5c16e380d8ac36c8430e4dcca81ab57fe7
tree    e1ac9330caa88f2bbefe5ff4d4cf1d70fc40a20a
parent  60630dbdb99c37193673c100c7239c80a8b90527
pymacadmin / lib / PyMacAdmin / Security / Keychain.py lib/PyMacAdmin/Security/Keychain.py
100755 334 lines (269 sloc) 12.914 kb
edit raw blame history
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
#!/usr/bin/env python
# encoding: utf-8
"""
Wrapper for the core Keychain API
 
Most of the internals are directly based on the native Keychain API. Apple's developer documentation is highly relevant:
 
http://developer.apple.com/documentation/Security/Reference/keychainservices/Reference/reference.html#//apple_ref/doc/uid/TP30000898-CH1-SW1
"""
 
import os
import ctypes
from PyMacAdmin import Security
 
class Keychain(object):
    """A friendlier wrapper for the Keychain API"""
    # TODO: Add support for SecKeychainSetUserInteractionAllowed
 
    def __init__(self, keychain_name=None):
        self.keychain_handle = self.open_keychain(keychain_name)
 
    def open_keychain(self, path=None):
        """Open a keychain file - if no path is provided, the user's default keychain will be used"""
        if not path:
            return None
 
        if path and not os.path.exists(path):
            raise IOError("Keychain %s does not exist" % path)
 
        keychain = ctypes.c_void_p()
        keychain_ptr = ctypes.pointer(keychain)
 
        rc = Security.lib.SecKeychainOpen(path, keychain_ptr)
        if rc != 0:
            raise RuntimeError("Couldn't open system keychain: rc=%d" % rc)
 
        return keychain
 
    def find_generic_password(self, service_name="", account_name=""):
        """Pythonic wrapper for SecKeychainFindGenericPassword"""
        item_p = ctypes.c_uint32()
        password_length = ctypes.c_uint32(0)
        password_data = ctypes.c_char_p(256)
 
        # For our purposes None and "" should be equivalent but we need a real
        # string for len() below:
        if not service_name:
            service_name = ""
        if not account_name:
            account_name = ""
 
        rc = Security.lib.SecKeychainFindGenericPassword (
            self.keychain_handle,
            len(service_name), # Length of service name
            service_name, # Service name
            len(account_name), # Account name length
            account_name, # Account name
            ctypes.byref(password_length), # Will be filled with pw length
            ctypes.pointer(password_data), # Will be filled with pw data
            ctypes.byref(item_p)
        )
 
        if rc == -25300:
            raise KeyError('No keychain entry for generic password: service=%s, account=%s' % (service_name, account_name))
        elif rc != 0:
            raise RuntimeError('Unable to retrieve generic password (service=%s, account=%s): rc=%d' % (service_name, account_name, rc))
 
        password = password_data.value[0:password_length.value]
 
        # itemRef: A reference to the keychain item from which you wish to
        # retrieve data or attributes.
        #
        # info: A pointer to a list of tags of attributes to retrieve.
        #
        # itemClass: A pointer to the item’s class. You should pass NULL if not
        # required. See “Keychain Item Class Constants” for valid constants.
        #
        # attrList: On input, the list of attributes in this item to get; on
        # output the attributes are filled in. You should call the function
        # SecKeychainItemFreeAttributesAndData when you no longer need the
        # attributes and data.
        #
        # length: On return, a pointer to the actual length of the data.
        #
        # outData: A pointer to a buffer containing the data in this item. Pass
        # NULL if not required. You should call the function
        # SecKeychainItemFreeAttributesAndData when you no longer need the
        # attributes and data.
 
        info = SecKeychainAttributeInfo()
        attrs_p = SecKeychainAttributeList_p()
 
        # Thank you Wil Shipley:
        # http://www.wilshipley.com/blog/2006/10/pimp-my-code-part-12-frozen-in.html
        info.count = 1
        info.tag.contents = Security.kSecLabelItemAttr
 
        Security.lib.SecKeychainItemCopyAttributesAndData(item_p, ctypes.pointer(info), None, ctypes.byref(attrs_p), None, None)
        attrs = attrs_p.contents
        assert(attrs.count == 1)
 
        label = attrs.attr[0].data[:attrs.attr[0].length]
 
        Security.lib.SecKeychainFreeAttributeInfo(attrs_p)
 
        return GenericPassword(service_name=service_name, account_name=account_name, password=password, keychain_item=item_p, label=label)
 
    def find_internet_password(self, account_name="", password="", server_name="", security_domain="", path="", port=0, protocol_type=None, authentication_type=None):
        """Pythonic wrapper for SecKeychainFindInternetPassword"""
        item = ctypes.c_void_p()
        password_length = ctypes.c_uint32(0)
        password_data = ctypes.c_char_p(256)
 
        if protocol_type and len(protocol_type) != 4:
            raise TypeError("protocol_type must be a valid FourCharCode - see http://developer.apple.com/documentation/Security/Reference/keychainservices/Reference/reference.html#//apple_ref/doc/c_ref/SecProtocolType")
 
        if authentication_type and len(authentication_type) != 4:
            raise TypeError("authentication_type must be a valid FourCharCode - see http://developer.apple.com/documentation/Security/Reference/keychainservices/Reference/reference.html#//apple_ref/doc/c_ref/SecAuthenticationType")
 
        if not isinstance(port, int):
            port = int(port)
 
        rc = Security.lib.SecKeychainFindInternetPassword(
            self.keychain_handle,
            len(server_name),
            server_name,
            len(security_domain) if security_domain else 0,
            security_domain,
            len(account_name),
            account_name,
            len(path),
            path,
            port,
            protocol_type,
            authentication_type,
            ctypes.byref(password_length), # Will be filled with pw length
            ctypes.pointer(password_data), # Will be filled with pw data
            ctypes.pointer(item)
        )
 
        if rc == -25300:
            raise KeyError('No keychain entry for internet password: server=%s, account=%s' % (server_name, account_name))
        elif rc != 0:
            raise RuntimeError('Unable to retrieve internet password (server=%s, account=%s): rc=%d' % (server_name, account_name, rc))
 
        password = password_data.value[0:password_length.value]
 
        Security.lib.SecKeychainItemFreeContent(None, password_data)
 
        return InternetPassword(server_name=server_name, account_name=account_name, password=password, keychain_item=item, security_domain=security_domain, path=path, port=port, protocol_type=protocol_type, authentication_type=authentication_type)
 
    def add(self, item):
        """Add the provided GenericPassword or InternetPassword object to this Keychain"""
        assert(isinstance(item, GenericPassword))
 
        item_ref = ctypes.c_void_p()
 
        if isinstance(item, InternetPassword):
            rc = Security.lib.SecKeychainAddInternetPassword(
                self.keychain_handle,
                len(item.server_name),
                item.server_name,
                len(item.security_domain),
                item.security_domain,
                len(item.account_name),
                item.account_name,
                len(item.path),
                item.path,
                item.port,
                item.protocol_type,
                item.authentication_type,
                len(item.password),
                item.password,
                ctypes.pointer(item_ref)
            )
        else:
            rc = Security.lib.SecKeychainAddGenericPassword(
                self.keychain_handle,
                len(item.service_name),
                item.service_name,
                len(item.account_name),
                item.account_name,
                len(item.password),
                item.password,
                ctypes.pointer(item_ref)
            )
 
        if rc != 0:
            raise RuntimeError("Error adding %s: rc=%d" % (item, rc))
 
        item.keychain_item = item_ref
 
    def remove(self, item):
        """Remove the provided keychain item as the reverse of Keychain.add()"""
        assert(isinstance(item, GenericPassword))
        item.delete()
 
 
class GenericPassword(object):
    """Generic keychain password used with SecKeychainAddGenericPassword and SecKeychainFindGenericPassword"""
    # TODO: Add support for access control and attributes
 
    account_name = None
    service_name = None
    label = None
    password = None
    keychain_item = None # An SecKeychainItemRef treated as an opaque object
 
    def __init__(self, **kwargs):
        super(GenericPassword, self).__init__()
        for k, v in kwargs.items():
            if not hasattr(self, k):
                raise AttributeError("Unknown property %s" % k)
            setattr(self, k, v)
 
    def update_password(self, new_password):
        """Change the stored password"""
 
        rc = Security.lib.SecKeychainItemModifyAttributesAndData(
            self.keychain_item,
            None,
            len(new_password),
            new_password
        )
 
        if rc == -61:
            raise RuntimeError("Permission denied updating %s" % self)
        elif rc != 0:
            raise RuntimeError("Unable to update password for %s: rc = %d" % rc)
 
    def delete(self):
        """Removes this item from the keychain"""
        rc = Security.lib.SecKeychainItemDelete(self.keychain_item)
        if rc != 0:
            raise RuntimeError("Unable to delete %s: rc=%d" % (self, rc))
 
        from CoreFoundation import CFRelease
        CFRelease(self.keychain_item)
 
        self.keychain_item = None
        self.service_name = None
        self.account_name = None
        self.password = None
 
    def __str__(self):
        return repr(self)
 
    def __repr__(self):
        props = []
        for k in ['service_name', 'account_name', 'label']:
            props.append("%s=%s" % (k, repr(getattr(self, k))))
 
        return "%s(%s)" % (self.__class__.__name__, ", ".join(props))
 
 
class InternetPassword(GenericPassword):
    """Specialized keychain item for internet passwords used with SecKeychainAddInternetPassword and SecKeychainFindInternetPassword"""
    account_name = ""
    password = None
    keychain_item = None
    server_name = ""
    security_domain = ""
    path = ""
    port = 0
    protocol_type = None
    authentication_type = None
 
    def __init__(self, **kwargs):
        super(InternetPassword, self).__init__(**kwargs)
 
    def __repr__(self):
        props = []
        for k in ['account_name', 'server_name', 'security_domain', 'path', 'port', 'protocol_type', 'authentication_type']:
            if getattr(self, k):
                props.append("%s=%s" % (k, repr(getattr(self, k))))
 
        return "%s(%s)" % (self.__class__.__name__, ", ".join(props))
 
class SecKeychainAttribute(ctypes.Structure):
    """Contains keychain attributes
 
tag: A 4-byte attribute tag.
length: The length of the buffer pointed to by data.
data: A pointer to the attribute data.
"""
    _fields_ = [
        ('tag', ctypes.c_uint32),
        ('length', ctypes.c_uint32),
        ('data', ctypes.c_char_p)
    ]
 
class SecKeychainAttributeList(ctypes.Structure):
    """Represents a list of keychain attributes
 
count: An unsigned 32-bit integer that represents the number of keychain attributes in the array.
attr: A pointer to the first keychain attribute in the array.
"""
 
    # TODO: Standard iterator support for SecKeychainAttributeList:
    #
    # for offset in range(0, attrs.count):
    # print "[%d]: %s: %s" % (offset, attrs.attr[offset].tag, attrs.attr[offset].data[:attrs.attr[offset].length])
    #
    # becomes:
    #
    # for tag, data in attrs:
    # …
    #
    # attrs[tag] should also work
    #
 
    _fields_ = [
        ('count', ctypes.c_uint),
        ('attr', ctypes.POINTER(SecKeychainAttribute))
    ]
 
class SecKeychainAttributeInfo(ctypes.Structure):
    """Represents a keychain attribute as a pair of tag and format values.
 
count: The number of tag-format pairs in the respective arrays
tag: A pointer to the first attribute tag in the array
format: A pointer to the first CSSM_DB_ATTRIBUTE_FORMAT in the array
"""
    # TODO: SecKeychainAttributeInfo should allow .append(tag, [data])
    _fields_ = [
        ('count', ctypes.c_uint),
        ('tag', ctypes.POINTER(ctypes.c_uint)),
        ('format', ctypes.POINTER(ctypes.c_uint))
    ]
 
# The APIs expect pointers to SecKeychainAttributeInfo objects:
SecKeychainAttributeInfo_p = ctypes.POINTER(SecKeychainAttributeInfo)
SecKeychainAttributeList_p = ctypes.POINTER(SecKeychainAttributeList)
 
Blog | Support | Training | Contact | API | Status | Twitter | Help | Security
© 2010 GitHub Inc. All rights reserved. | Terms of Service | Privacy Policy
Powered by the Dedicated Servers and
Cloud Computing of Rackspace Hosting®
Dedicated Server