def check_keychain_identity(identity, allowInteraction=False): """ Verify that the Keychain identity exists and that the private key is accessible. @param identity: identity value to match @type identity: L{str} @return: empty L{str} if OK, error message if not @rtype: L{str} """ # Always turn off user interaction security.SecKeychainSetUserInteractionAllowed(allowInteraction) try: secidentity = load_keychain_identity(identity) except Error: return "Unable to load Keychain identity: {}".format(identity) pkey = ffi.new("SecKeyRef *") err = security.SecIdentityCopyPrivateKey(secidentity.ref(), pkey) if err != 0: return "Unable to load private key for Keychain identity: {}".format( identity) pkey = CFObjectRef(pkey[0]) # Try to sign some data with the pkey to check we have access error = ffi.new("CFErrorRef *") signer = security.SecSignTransformCreate(pkey.ref(), error) if error[0] != ffi.NULL: cferror = CFErrorRef(error[0]) return "Unable to use private key for Keychain identity: {} - {}".format( identity, cferror.description()) signer = CFObjectRef(signer) signMe = CFDataRef.fromString("sign me") security.SecTransformSetAttribute(signer.ref(), security.kSecTransformInputAttributeName, signMe.ref(), error) if error[0] != ffi.NULL: cferror = CFErrorRef(error[0]) return "Unable to use private key for Keychain identity: {} - {}".format( identity, cferror.description()) signature = security.SecTransformExecute(signer.ref(), error) if error[0] != ffi.NULL or signature == ffi.NULL: cferror = CFErrorRef(error[0]) return "Unable to use private key for Keychain identity: {} - {}".format( identity, cferror.description()) signature = CFObjectRef(signature) return ""
def _load_keychain_item(identifier): """ Load a certificate with the supplied identity string. @param identifier: name of the KeyChain item to lookup @type identifier: L{str} @return: the certificate @rtype: L{X509} """ # First try to get the identity from the KeyChain name = CFStringRef.fromString(identifier) certificate = security.SecCertificateCopyPreferred(name.ref(), ffi.NULL) if certificate == ffi.NULL: try: identity = load_keychain_identity(identifier) except Error: raise Error( "Identity for preferred name '{}' was not found".format( identifier)) certificate = ffi.new("SecCertificateRef *") err = security.SecIdentityCopyCertificate(identity.ref(), certificate) if err != 0: raise Error( "Identity for preferred name '{}' was not found".format( identifier)) certificate = certificate[0] certificate = CFObjectRef(certificate) return X509(certificate)
def _load_certificate_data(certtype, buffer, result_typeid): """ Load a certificate with the supplied type and data. @param certtype: ignored @type certtype: - @param buffer: name of the KeyChain item to lookup @type buffer: L{str} @param result_typeid: The type to return (certificate or key) @type result_typeid: L{ffi.CFTypeID} @return: the certificate @rtype: L{X509} """ # First try to get the identity from the KeyChain data = CFDataRef.fromString(buffer) results = ffi.new("CFArrayRef *") err = security.SecItemImport(data.ref(), ffi.NULL, ffi.NULL, ffi.NULL, 0, ffi.NULL, ffi.NULL, results) if err != 0: raise Error("Could not load certificate data") results = CFArrayRef(results[0]).toList() # Try to find a SecCertificateRef for result in results: if result.instanceTypeId() == result_typeid: return result else: raise Error("No certificate in data")
def connect(self): """ Create the SecureTransport SSLContextRef object and initialize it. """ self.ctx = security.SSLCreateContext(ffi.NULL, security.kSSLClientSide if self.is_client else security.kSSLServerSide, security.kSSLStreamType) minVersion = None for option, minValue in ( (OP_NO_SSLv2, security.kSSLProtocol3), (OP_NO_SSLv3, security.kTLSProtocol1), (OP_NO_TLSv1, security.kTLSProtocol11), (OP_NO_TLSv1_1, security.kTLSProtocol12), (OP_NO_TLSv1_2, security.kTLSProtocol12), # TLS1.2 is the highest supported right now ): if option in self.context.options: minVersion = minValue if minVersion is not None: security.SSLSetProtocolVersionMin(self.ctx, minVersion) # Make sure we have a reference back to this L{Connection} in the SecureTransport callbacks self.connref = ffi.new("int *", self.engine_id) err = security.SSLSetConnection(self.ctx, ffi.cast("SSLConnectionRef", self.connref)) if err: self.shutdown() raise Error(err) # Setup the actual SecureTransport callbacks err = security.SSLSetIOFuncs(self.ctx, self._read, self._write) if err: self.shutdown() raise Error(err) # Must have a certificate identity if we are a server if not self.is_client and self.context.identity is None: self.shutdown() raise Error("No certificate") # Must have a peer name if we are a client if self.is_client and not self.context.peerName: self.shutdown() raise Error("No peer name set with client connection.") elif self.is_client: # Always set the client peer name for proper certificate validation err = security.SSLSetPeerDomainName(self.ctx, self.context.peerName, len(self.context.peerName)) if err: self.shutdown() raise Error(err) # Add the certificate if self.context.identity is not None: certs = CFArrayRef.fromList([self.context.identity]) err = security.SSLSetCertificate(self.ctx, certs.ref()) if err: self.shutdown() raise Error(err)
def _sslread(self, bytes): """ Wrapper for SecureTransport SSLRead method. @param bytes: maximum length of data to read @type bytes: L{int} @return: the data read @rtype: L{ffi.buffer} """ read = ffi.new("char[]", bytes) processed = ffi.new("size_t *") err = security.SSLRead(self.ctx, read, bytes, processed) if err == 0: response = ffi.buffer(read, processed[0]) return response elif err == security.errSSLWouldBlock: raise WantReadError elif err == security.errSSLClosedGraceful: raise ZeroReturnError else: self.shutdown() raise Error(err)
def _sslwrite(self, data): """ Wrapper for SecureTransport SSLWrite method. @param data: data to write @type data: L{str} """ processed = ffi.new("size_t *") err = security.SSLWrite(self.ctx, data, len(data), processed) if err == 0: return processed[0] elif err == security.errSSLWouldBlock: return processed[0] else: self.shutdown() raise Error(err)
def load_keychain_identity(identity): """ Retrieve a SecIdentityRef from the KeyChain with a identity that exactly matches the passed in value. @param identity: identity value to match @type identity: L{str} @return: matched SecIdentityRef item or L{None} @rtype: L{CFObjectRef} """ # First try to load this from an identity preference cfsubject = CFStringRef.fromString(identity) secidentity = security.SecIdentityCopyPreferred(cfsubject.ref(), ffi.NULL, ffi.NULL) if secidentity != ffi.NULL: return CFObjectRef(secidentity) # Now iterate items to find a match match = CFDictionaryRef.fromDict({ CFStringRef.fromRef(security.kSecClass): CFStringRef.fromRef(security.kSecClassIdentity), CFStringRef.fromRef(security.kSecReturnRef): CFBooleanRef.fromBool(True), CFStringRef.fromRef(security.kSecReturnAttributes): CFBooleanRef.fromBool(True), CFStringRef.fromRef(security.kSecMatchLimit): CFStringRef.fromRef(security.kSecMatchLimitAll), }) result = ffi.new("CFTypeRef *") err = security.SecItemCopyMatching(match.ref(), result) if err != 0: return None result = CFArrayRef(result[0]) for item in result.toList(): if item[str(CFStringRef.fromRef(security.kSecAttrLabel))] == identity: secidentity = item[str(CFStringRef.fromRef(security.kSecValueRef))] break else: raise Error("Could not find Keychain identity: {}".format(identity)) return secidentity
def get_subject(self): """ Use Security.framework to extract the componentized SubjectName field and map OID values to strings and store in an L{X509Name} object. """ keys = CFArrayRef.fromList( [CFStringRef.fromRef(security.kSecOIDX509V1SubjectName)]) error = ffi.new("CFErrorRef *") values = security.SecCertificateCopyValues(self._x509.ref(), keys.ref(), error) if values == ffi.NULL: error = CFErrorRef(error[0]) raise Error("Unable to get certificate subject") values = CFDictionaryRef(values).toDict() value = values[str( CFStringRef.fromRef(security.kSecOIDX509V1SubjectName))] components = {} if value[str(CFStringRef.fromRef( security.kSecPropertyKeyType))] == str( CFStringRef.fromRef(security.kSecPropertyTypeSection)): for item in value[str( CFStringRef.fromRef(security.kSecPropertyKeyValue))]: if item[str(CFStringRef.fromRef( security.kSecPropertyKeyType))] == str( CFStringRef.fromRef( security.kSecPropertyTypeString)): v = item[str( CFStringRef.fromRef(security.kSecPropertyKeyValue))] k = OID2STR.get( item[str( CFStringRef.fromRef( security.kSecPropertyKeyLabel))], "Unknown") components[k] = v return X509Name("Subject Name", components)
def test_cms_sign_verify_ok(self): """ Use the simple encode. """ # Sign some data sign = "Something to be signed" result = ffi.new("CFDataRef *") signer = load_keychain_identity("org.calendarserver.test") error = cms.CMSEncodeContent( signer.ref(), ffi.NULL, ffi.NULL, False, cms.kCMSAttrNone, sign, len(sign), result, ) self.assertEqual(error, 0) result = CFDataRef(result[0]) self.assertNotEqual(result.count(), 0) # Now verify the result decoder = ffi.new("CMSDecoderRef *") error = cms.CMSDecoderCreate(decoder) self.assertEqual(error, 0) decoder = CFObjectRef(decoder[0]) error = cms.CMSDecoderUpdateMessage(decoder.ref(), result.toString(), result.count()) self.assertEqual(error, 0) error = cms.CMSDecoderFinalizeMessage(decoder.ref()) self.assertEqual(error, 0) number = ffi.new("size_t *") error = cms.CMSDecoderGetNumSigners(decoder.ref(), number) self.assertEqual(error, 0) self.assertEqual(number[0], 1) encrypted = ffi.new("Boolean *") error = cms.CMSDecoderIsContentEncrypted(decoder.ref(), encrypted) self.assertEqual(error, 0) self.assertEqual(encrypted[0], False) policy = cms.SecPolicyCreateBasicX509() policy = CFObjectRef(policy) status = ffi.new("CMSSignerStatus *") verify_result = ffi.new("OSStatus *") error = cms.CMSDecoderCopySignerStatus( decoder.ref(), 0, policy.ref(), True, status, ffi.NULL, verify_result, ) self.assertEqual(error, 0) self.assertEqual(status[0], cms.kCMSSignerValid) self.assertEqual(verify_result[0], 0) result = ffi.new("CFDataRef *") error = cms.CMSDecoderCopyContent(decoder.ref(), result) self.assertEqual(error, 0) result = CFDataRef(result[0]) self.assertEqual(result.toString(), sign)