Source code for pyndn.security.identity.osx_private_key_storage

# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
#
# Copyright (C) 2014-2016 Regents of the University of California.
# Author: Jeff Thompson <jefft0@remap.ucla.edu>
# From ndn-cxx security by Yingdi Yu <yingdi@cs.ucla.edu>.
# This uses cocoapy in pyglet http://www.pyglet.org/. See contrib/cocoapy/LICENSE
#
# 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 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# A copy of the GNU Lesser General Public License is in the file COPYING.

"""
This module defines the OSXPrivateKeyStorage class which extends
PrivateKeyStorage to implement private key storage using the OS X Keychain.
"""

import sys
import logging
if sys.platform == 'darwin':
    from pyndn.contrib.cocoapy import *
from pyndn.util.blob import Blob
from pyndn.security.certificate import PublicKey
from pyndn.security.security_types import DigestAlgorithm
from pyndn.security.security_types import KeyClass
from pyndn.security.security_types import KeyType
from pyndn.security.security_exception import SecurityException
from pyndn.security.identity.private_key_storage import PrivateKeyStorage

[docs]class OSXPrivateKeyStorage(PrivateKeyStorage): def __init__(self): super(OSXPrivateKeyStorage, self).__init__() #pylint: disable=E1103 self._kCFBooleanTrue = c_void_p.in_dll(cf, "kCFBooleanTrue") self._security = cdll.LoadLibrary( "/System/Library/Frameworks/Security.framework/Versions/Current/Security") self._security.SecItemCopyMatching.restype = c_void_p self._security.SecItemCopyMatching.argtypes = [c_void_p, POINTER(c_void_p)] self._security.SecSignTransformCreate.restype = c_void_p self._security.SecSignTransformCreate.argtypes = [c_void_p, POINTER(c_void_p)] self._security.SecTransformSetAttribute.restype = c_void_p self._security.SecTransformSetAttribute.argtypes = [c_void_p, c_void_p, c_void_p, POINTER(c_void_p)] self._security.SecTransformExecute.restype = c_void_p self._security.SecTransformExecute.argtypes = [c_void_p, POINTER(c_void_p)] self._security.SecItemExport.restype = c_void_p self._security.SecItemExport.argtypes = [c_void_p, c_void_p, c_void_p, c_void_p, POINTER(c_void_p)] self._kSecClass = c_void_p.in_dll(self._security, "kSecClass") self._kSecClassKey = c_void_p.in_dll(self._security, "kSecClassKey") self._kSecAttrKeyType = c_void_p.in_dll(self._security, "kSecAttrKeyType") self._kSecAttrKeySizeInBits = c_void_p.in_dll(self._security, "kSecAttrKeySizeInBits") self._kSecAttrLabel = c_void_p.in_dll(self._security, "kSecAttrLabel") self._kSecAttrKeyClass = c_void_p.in_dll(self._security, "kSecAttrKeyClass") self._kSecReturnRef = c_void_p.in_dll(self._security, "kSecReturnRef") self._kSecMatchLimit = c_void_p.in_dll(self._security, "kSecMatchLimit") self._kSecMatchLimitAll = c_void_p.in_dll(self._security, "kSecMatchLimitAll") self._kSecAttrKeyTypeAES = c_void_p.in_dll(self._security, "kSecAttrKeyTypeAES") self._kSecAttrKeyTypeRSA = c_void_p.in_dll(self._security, "kSecAttrKeyTypeRSA") self._kSecAttrKeyTypeECDSA = c_void_p.in_dll(self._security, "kSecAttrKeyTypeECDSA") self._kSecAttrKeyClassPrivate = c_void_p.in_dll(self._security, "kSecAttrKeyClassPrivate") self._kSecAttrKeyClassPublic = c_void_p.in_dll(self._security, "kSecAttrKeyClassPublic") self._kSecAttrKeyClassSymmetric = c_void_p.in_dll(self._security, "kSecAttrKeyClassSymmetric") self._kSecDigestSHA2 = c_void_p.in_dll(self._security, "kSecDigestSHA2") self._kSecTransformInputAttributeName = c_void_p.in_dll(self._security, "kSecTransformInputAttributeName") self._kSecDigestTypeAttribute = c_void_p.in_dll(self._security, "kSecDigestTypeAttribute") self._kSecDigestLengthAttribute = c_void_p.in_dll(self._security, "kSecDigestLengthAttribute") #pylint: enable=E1103 # enum values: self._kSecFormatOpenSSL = 1
[docs] def generateKeyPair(self, keyName, params): """ Generate a pair of asymmetric keys. :param Name keyName: The name of the key pair. :param KeyParams params: The parameters of the key. """ if self.doesKeyExist(keyName, KeyClass.PUBLIC): raise SecurityException("keyName already exists") keyLabel = None attrDict = None cfKeySize = None publicKey = None privateKey = None try: keyNameUri = self._toInternalKeyName(keyName, KeyClass.PUBLIC) keyLabel = CFSTR(keyNameUri) attrDict = c_void_p(cf.CFDictionaryCreateMutable( None, 3, cf.kCFTypeDictionaryKeyCallBacks, None)) if params.getKeyType() == KeyType.RSA: keySize = params.getKeySize() elif params.getKeyType() == KeyType.ECDSA: keySize = params.getKeySize() else: raise SecurityException("generateKeyPair: Unsupported key type ") cfKeySize = c_void_p(cf.CFNumberCreate( None, kCFNumberIntType, byref(c_int(keySize)))) cf.CFDictionaryAddValue( attrDict, self._kSecAttrKeyType, self._getAsymmetricKeyType(params.getKeyType())) cf.CFDictionaryAddValue( attrDict, self._kSecAttrKeySizeInBits, cfKeySize) cf.CFDictionaryAddValue( attrDict, self._kSecAttrLabel, keyLabel) publicKey = c_void_p() privateKey = c_void_p() res = self._security.SecKeyGeneratePair( attrDict, pointer(publicKey), pointer(privateKey)) if res != 0: raise SecurityException("Fail to create a key pair") finally: if keyLabel != None: cf.CFRelease(keyLabel) if attrDict != None: cf.CFRelease(attrDict) if cfKeySize != None: cf.CFRelease(cfKeySize) if publicKey != None: cf.CFRelease(publicKey) if privateKey != None: cf.CFRelease(privateKey)
[docs] def deleteKeyPair(self, keyName): """ Delete a pair of asymmetric keys. If the key doesn't exist, do nothing. :param Name keyName: The name of the key pair. """ keyLabel = None searchDict = None try: keyLabel = CFSTR(keyName.toUri()) searchDict = c_void_p(cf.CFDictionaryCreateMutable( None, 5, cf.kCFTypeDictionaryKeyCallBacks, None)) cf.CFDictionaryAddValue( searchDict, self._kSecClass, self._kSecClassKey) cf.CFDictionaryAddValue( searchDict, self._kSecAttrLabel, keyLabel) cf.CFDictionaryAddValue( searchDict, self._kSecMatchLimit, self._kSecMatchLimitAll) self._security.SecItemDelete(searchDict) finally: if keyLabel != None: cf.CFRelease(keyLabel) if searchDict != None: cf.CFRelease(searchDict)
[docs] def getPublicKey(self, keyName): """ Get the public key with the keyName. :param Name keyName: The name of public key. :return: The public key. :rtype: PublicKey """ publicKey = self._getKey(keyName, KeyClass.PUBLIC) if publicKey == None: raise SecurityException( "The requested public key [" + keyName.toUri() + "] does not exist in the OSX Keychain") exportedKey = None try: exportedKey = c_void_p() res = self._security.SecItemExport( publicKey, self._kSecFormatOpenSSL, 0, None, pointer(exportedKey)) if res != None: raise SecurityException( "Cannot export the requested public key from the OSX Keychain") blob = self._CFDataToBlob(exportedKey) return PublicKey(blob) finally: if publicKey != None: cf.CFRelease(publicKey) if exportedKey != None: cf.CFRelease(exportedKey)
[docs] def sign(self, data, keyName, digestAlgorithm = DigestAlgorithm.SHA256): """ Fetch the private key for keyName and sign the data, returning a signature Blob. :param data: Pointer the input byte buffer to sign. :type data: An array type with int elements :param Name keyName: The name of the signing key. :param digestAlgorithm: (optional) the digest algorithm. If omitted, use DigestAlgorithm.SHA256. :type digestAlgorithm: int from DigestAlgorithm :return: The signature Blob. :rtype: Blob """ privateKey = self._getKey(keyName, KeyClass.PRIVATE) if privateKey == None: raise SecurityException("private key not found") signer = None dataRef = None digestSizeRef = None try: error = c_void_p() signer = self._security.SecSignTransformCreate( privateKey, pointer(error)) if error.value != None: raise SecurityException("Failed to create the signer") # we need a str. Use Blob to convert data. dataStr = Blob(data, False).toRawStr() dataRef = c_void_p(cf.CFDataCreate(None, dataStr, len(data))) self._security.SecTransformSetAttribute( signer, self._kSecTransformInputAttributeName, dataRef, pointer(error)) if error.value != None: raise SecurityException("Failed to configure the input of the signer") self._security.SecTransformSetAttribute( signer, self._kSecDigestTypeAttribute, self._getDigestAlgorithm(digestAlgorithm), pointer(error)) if error.value != None: raise SecurityException("Fail to configure the digest algorithm of the signer") digestSizeRef = c_void_p(cf.CFNumberCreate( None, kCFNumberLongType, byref(c_long(self._getDigestSize(digestAlgorithm))))) self._security.SecTransformSetAttribute( signer, self._kSecDigestLengthAttribute, digestSizeRef, pointer(error)) if error.value != None: raise SecurityException("Failed to configure the digest size of the signer") signature = self._security.SecTransformExecute( signer, pointer(error)) if error.value != None: raise SecurityException("Failed to sign the data") if signature == None: raise SecurityException("Signature is NULL!") return self._CFDataToBlob(signature) finally: if privateKey != None: cf.CFRelease(privateKey) if signer != None: cf.CFRelease(signer) if dataRef != None: cf.CFRelease(dataRef) if digestSizeRef != None: cf.CFRelease(digestSizeRef) if signature != None: cf.CFRelease(signature)
[docs] def decrypt(self, keyName, data, isSymmetric = False): """ Decrypt data. :param Name keyName: The name of the decrypting key. :param data: The byte buffer to be decrypted. :type data: An array type with int elements :param bool isSymmetric: (optional) If True symmetric encryption is used, otherwise asymmetric encryption is used. If omitted, use asymmetric encryption. :return: The decrypted data. :rtype: Blob """ raise RuntimeError("decrypt is not implemented")
[docs] def encrypt(self, keyName, data, isSymmetric = False): """ Encrypt data. :param Name keyName: The name of the encrypting key. :param data: The byte buffer to be encrypted. :type data: An array type with int elements :param bool isSymmetric: (optional) If True symmetric encryption is used, otherwise asymmetric encryption is used. If omitted, use asymmetric encryption. :return: The encrypted data. :rtype: Blob """ raise RuntimeError("encrypt is not implemented")
[docs] def generateKey(self, keyName, params): """ Generate a symmetric key. :param Name keyName: The name of the key. :param KeyParams params: The parameters of the key. """ raise RuntimeError("generateKey is not implemented")
[docs] def doesKeyExist(self, keyName, keyClass): """ Check if a particular key exists. :param Name keyName: The name of the key. :param keyClass: The class of the key, e.g. KeyClass.PUBLIC, KeyClass.PRIVATE, or KeyClass.SYMMETRIC. :type keyClass: int from KeyClass :return: True if the key exists, otherwise false. :rtype: bool """ keyLabel = None attrDict = None try: keyNameUri = self._toInternalKeyName(keyName, keyClass) keyLabel = CFSTR(keyNameUri) attrDict = c_void_p(cf.CFDictionaryCreateMutable( None, 4, cf.kCFTypeDictionaryKeyCallBacks, None)) cf.CFDictionaryAddValue( attrDict, self._kSecClass, self._kSecClassKey) cf.CFDictionaryAddValue( attrDict, self._kSecAttrLabel, keyLabel) cf.CFDictionaryAddValue( attrDict, self._kSecReturnRef, self._kCFBooleanTrue) itemRef = c_void_p() res = self._security.SecItemCopyMatching(attrDict, pointer(itemRef)) return res == None finally: if keyLabel != None: cf.CFRelease(keyLabel) if attrDict != None: cf.CFRelease(attrDict)
@staticmethod def _toInternalKeyName(keyName, keyClass): """ Convert an NDN name of a key to an internal name of the key base on the keyClass. :param Name keyName: The NDN name of the key. :param keyClass: The class of the key, e.g. KeyClass.PUBLIC, KeyClass.PRIVATE, or KeyClass.SYMMETRIC. :type keyClass: int from KeyClass :return: The internal key name. :rtype: str """ keyUri = keyName.toUri() if KeyClass.SYMMETRIC == keyClass: return keyUri + "/symmetric" else: return keyUri def _getKey(self, keyName, keyClass): """ Get a key from the Keychain. :param Name keyName: The name of the key. :param keyClass: The class of the key, e.g. KeyClass.PUBLIC, KeyClass.PRIVATE, or KeyClass.SYMMETRIC. :return: None if not found, otherwise a Keychain ref to the key. You must use cf.CFRelease to free it. :rtype: c_void_p """ keyLabel = None attrDict = None try: keyNameUri = self._toInternalKeyName(keyName, keyClass) keyLabel = CFSTR(keyNameUri) attrDict = c_void_p(cf.CFDictionaryCreateMutable( None, 5, cf.kCFTypeDictionaryKeyCallBacks, None)) cf.CFDictionaryAddValue( attrDict, self._kSecClass, self._kSecClassKey) cf.CFDictionaryAddValue( attrDict, self._kSecAttrLabel, keyLabel) cf.CFDictionaryAddValue( attrDict, self._kSecAttrKeyClass, self._getKeyClass(keyClass)) cf.CFDictionaryAddValue( attrDict, self._kSecReturnRef, self._kCFBooleanTrue) keyItem = c_void_p() res = self._security.SecItemCopyMatching(attrDict, pointer(keyItem)) if res != None: logging.getLogger(__name__).debug("Fail to find the key!"); return None else: return keyItem finally: if keyLabel != None: cf.CFRelease(keyLabel) if attrDict != None: cf.CFRelease(attrDict) def _getSymmetricKeyType(self, keyType): """ Convert keyType to the MAC OS symmetric key key type. :param keyType: The type of the key. :type keyType: int from KeyType :return: The MAC OS key type. :rtype: c_void_p """ if keyType == KeyType.AES: return self._kSecAttrKeyTypeAES else: logging.getLogger(__name__).debug("Unrecognized key type!") return None def _getAsymmetricKeyType(self, keyType): """ Convert keyType to the MAC OS asymmetric key key type. :param keyType: The type of the key. :type keyType: int from KeyType :return: The MAC OS key type. :rtype: c_void_p """ if keyType == KeyType.RSA: return self._kSecAttrKeyTypeRSA elif keyType == KeyType.ECDSA: return self._kSecAttrKeyTypeECDSA else: logging.getLogger(__name__).debug("Unrecognized key type!") return None def _getKeyClass(self, keyClass): """ Convert keyClass to the Mac OS key class. :param keyClass: The class of the key, e.g. KeyClass.PUBLIC, KeyClass.PRIVATE, or KeyClass.SYMMETRIC. :type keyClass: int from KeyClass :return: The MAC OS key class. :rtype: c_void_p """ if keyClass == KeyClass.PRIVATE: return self._kSecAttrKeyClassPrivate elif keyClass == KeyClass.PUBLIC: return self._kSecAttrKeyClassPublic elif keyClass == KeyClass.SYMMETRIC: return self._kSecAttrKeyClassSymmetric else: logging.getLogger(__name__).debug("Unrecognized key class!") return None def _getDigestAlgorithm(self, digestAlgorithm): """ Convert digestAlgorithm to the MAC OS algorithm in. :param digestAlgorithm: The digest algorithm. :type digestAlgorithm: int from DigestAlgorithm :return: The MAC OS algorithm id. :rtype: c_void_p """ if digestAlgorithm == DigestAlgorithm.SHA256: return self._kSecDigestSHA2 else: logging.getLogger(__name__).debug("Unrecognized digest algorithm!") return None def _getDigestSize(self, digestAlgorithm): """ Get the digest size of the corresponding algorithm :param digestAlgorithm: The digest algorithm. :type digestAlgorithm: int from DigestAlgorithm :return: The digest size. :rtype: int """ if digestAlgorithm == DigestAlgorithm.SHA256: return 256 else: logging.getLogger(__name__).debug("Unrecognized digest algorithm!") return -1 @staticmethod def _CFDataToBlob(cfData): length = cf.CFDataGetLength(cfData) array = (c_byte * length)() cf.CFDataGetBytes(cfData, CFRange(0, length), array) # Convert from signed byte to unsigned byte. unsignedArray = [(x if x >= 0 else x + 256) for x in array] return Blob(unsignedArray, False)