/* * Copyright (c) 2008-2025 Jonathan Schleifer * * All rights reserved. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3.0 only, * as published by the Free Software Foundation. * * 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 * version 3.0 for more details. * * You should have received a copy of the GNU Lesser General Public License * version 3.0 along with this program. If not, see * . */ #include "config.h" #import "OFSecureTransportKeychain.h" #ifndef OF_IOS # import "OFIRI.h" # import "OFLocale.h" # ifdef OF_HAVE_THREADS # import "OFMutex.h" # endif # import "OFString.h" # import "OFSystemInfo.h" # import "OFUUID.h" # import "OFInitializationFailedException.h" /* * Apple deprecated Secure Transport without providing a replacement that can * work with any socket. On top of that, their replacement, Network.framework, * doesn't support STARTTLS at all. */ # if OF_GCC_VERSION >= 402 # pragma GCC diagnostic ignored "-Wdeprecated" # endif static OFSecureTransportKeychain *temporaryKeychain; # ifdef OF_HAVE_THREADS static OFMutex *temporaryKeychainMutex; # endif @implementation OFSecureTransportKeychain @synthesize keychain = _keychain; static void cleanup(void) { # ifdef OF_HAVE_THREADS [temporaryKeychainMutex lock]; @try { # endif if (temporaryKeychain != nil && temporaryKeychain->_keychain != NULL) SecKeychainDelete(temporaryKeychain->_keychain); # ifdef OF_HAVE_THREADS } @finally { [temporaryKeychainMutex unlock]; } # endif } + (void)initialize { if (self != [OFSecureTransportKeychain class]) return; # ifdef OF_HAVE_THREADS temporaryKeychainMutex = [[OFMutex alloc] init]; # endif atexit(cleanup); } + (instancetype)temporaryKeychain { OFSecureTransportKeychain *keychain; # ifdef OF_HAVE_THREADS [temporaryKeychainMutex lock]; @try { # endif if (temporaryKeychain != nil) keychain = temporaryKeychain; else { SecKeychainSettings settings = { .version = SEC_KEYCHAIN_SETTINGS_VERS1, .lockOnSleep = false, .useLockInterval = false, .lockInterval = INT_MAX }; void *pool; OFString *filename, *path, *password; keychain = [[[self alloc] init] autorelease]; pool = objc_autoreleasePoolPush(); filename = [OFString stringWithFormat: @"%@.keychain", [OFUUID UUID]]; # ifdef OF_HAVE_FILES path = [[OFSystemInfo temporaryDirectoryIRI] IRIByAppendingPathComponent: filename] .fileSystemRepresentation; # else path = [@"/tmp/" stringByAppendingString: filename]; # endif password = [OFString stringWithFormat: @"%08X%08X", OFRandom64(), OFRandom64()]; if (SecKeychainCreate([path cStringWithEncoding: [OFLocale encoding]], (UInt32)password.UTF8StringLength, password.UTF8String, NO, NULL, &keychain->_keychain) != noErr) @throw [OFInitializationFailedException exceptionWithClass: self]; /* * Make sure the keychain never gets locked * automatically, as on the next time it is being used, * the user would be asked to enter the keychain's * password via a popup - a password we randomly * generated and the user cannot know. */ if (SecKeychainSetSettings(keychain->_keychain, &settings) != noErr) @throw [OFInitializationFailedException exceptionWithClass: self]; objc_autoreleasePoolPop(pool); temporaryKeychain = keychain; } # ifdef OF_HAVE_THREADS } @finally { [temporaryKeychainMutex unlock]; } # endif return [[keychain retain] autorelease]; } - (void)dealloc { # ifdef OF_HAVE_THREADS [temporaryKeychainMutex lock]; @try { # endif if (self == temporaryKeychain) temporaryKeychain = nil; # ifdef OF_HAVE_THREADS } @finally { [temporaryKeychainMutex unlock]; } # endif if (_keychain != NULL) { SecKeychainDelete(_keychain); [(id)_keychain release]; } [super dealloc]; } @end #endif