/*
* Copyright (c) 2008-2025 Jonathan Schleifer <js@nil.im>
*
* 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
* <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#import <Foundation/Foundation.h>
#import "OFSecureTransportX509Certificate.h"
#import "OFArray.h"
#import "OFData.h"
#import "OFData+NSObject.h"
#ifndef OF_IOS
# import "OFSecureTransportKeychain.h"
#endif
#import "OFString.h"
#import "OFString+NSObject.h"
#include <Security/SecImportExport.h>
#import "OFInvalidFormatException.h"
#import "OFOutOfMemoryException.h"
#import "OFOutOfRangeException.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
#ifndef OF_IOS
static SecKeychainItemRef
privateKeyFromFile(OFIRI *IRI)
{
void *pool;
SecExternalFormat format = kSecFormatUnknown;
SecExternalItemType type = kSecItemTypePrivateKey;
OFSecureTransportKeychain *keychain;
NSData *data;
CFArrayRef items;
SecKeychainItemRef key;
if (IRI == nil)
return NULL;
pool = objc_autoreleasePoolPush();
keychain = [OFSecureTransportKeychain temporaryKeychain];
data = [[OFData dataWithContentsOfIRI: IRI] NSObject];
if (data == nil)
@throw [OFOutOfMemoryException exception];
if (SecKeychainItemImport((CFDataRef)data, NULL, &format, &type, 0,
NULL, keychain.keychain, &items) != noErr)
@throw [OFInvalidFormatException exception];
[(id)items autorelease];
if ([(id)items count] != 1)
@throw [OFInvalidFormatException exception];
key = (SecKeychainItemRef)[[(id)items objectAtIndex: 0] retain];
objc_autoreleasePoolPop(pool);
[(id)key autorelease];
return key;
}
#endif
@implementation OFSecureTransportX509Certificate
@synthesize of_certificate = _certificate;
#ifndef OF_IOS
@synthesize of_privateKey = _privateKey;
#endif
+ (void)load
{
if (OFX509CertificateImplementation == Nil)
OFX509CertificateImplementation = self;
}
+ (bool)supportsPEMFiles
{
#ifndef OF_IOS
return true;
#else
return false;
#endif
}
+ (bool)supportsPKCS12Files
{
return true;
}
#ifndef OF_IOS
+ (OFArray OF_GENERIC(OFX509Certificate *) *)
of_certificateChainFromFileAtIRI: (OFIRI *)IRI
privateKeyIRI: (OFIRI *)privateKeyIRI
passphrase: (OFString *)passphrase
format: (SecExternalFormat)format
type: (SecExternalItemType)type
{
OFMutableArray *chain = [OFMutableArray array];
void *pool = objc_autoreleasePoolPush();
OFSecureTransportKeychain *keychain =
[OFSecureTransportKeychain temporaryKeychain];
NSData *data = [[OFData dataWithContentsOfIRI: IRI] NSObject];
SecKeyImportExportParameters params = {
.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION
};
CFArrayRef items;
size_t i;
if (data == nil)
@throw [OFOutOfMemoryException exception];
if (passphrase != nil)
params.passphrase = passphrase.NSObject;
if (SecKeychainItemImport((CFDataRef)data, NULL, &format, &type, 0,
¶ms, keychain.keychain, &items) != noErr)
@throw [OFInvalidFormatException exception];
[(id)items autorelease];
i = 0;
for (id item_ in (NSArray *)items) {
SecCertificateRef item = (SecCertificateRef)item_;
SecKeychainItemRef key = NULL;
if (privateKeyIRI != nil && i == 0)
key = privateKeyFromFile(privateKeyIRI);
[chain addObject:
[[[self alloc] of_initWithCertificate: item
privateKey: key
keychain: keychain]
autorelease]];
i++;
}
[chain makeImmutable];
objc_autoreleasePoolPop(pool);
return chain;
}
+ (OFArray OF_GENERIC(OFX509Certificate *) *)
certificateChainFromPEMFileAtIRI: (OFIRI *)certificatesIRI
privateKeyIRI: (OFIRI *)privateKeyIRI
{
return [self of_certificateChainFromFileAtIRI: certificatesIRI
privateKeyIRI: privateKeyIRI
passphrase: nil
format: kSecFormatPEMSequence
type: kSecItemTypeCertificate];
}
+ (OFArray OF_GENERIC(OFX509Certificate *) *)
certificateChainFromPKCS12FileAtIRI: (OFIRI *)IRI
passphrase: (OFString *)passphrase
{
return [self of_certificateChainFromFileAtIRI: IRI
privateKeyIRI: nil
passphrase: passphrase
format: kSecFormatPKCS12
type: kSecItemTypeAggregate];
}
#else
+ (OFArray OF_GENERIC(OFX509Certificate *) *)
certificateChainFromPKCS12FileAtIRI: (OFIRI *)IRI
passphrase: (OFString *)passphrase
{
OFMutableArray *chain = [OFMutableArray array];
void *pool = objc_autoreleasePoolPush();
NSData *data = [[OFData dataWithContentsOfIRI: IRI] NSObject];
NSDictionary *options = nil;
CFArrayRef items;
if (data == nil)
@throw [OFOutOfMemoryException exception];
if (passphrase != nil) {
options = [NSDictionary
dictionaryWithObject: passphrase.NSObject
forKey: (NSString *)
kSecImportExportPassphrase];
if (options == nil)
@throw [OFOutOfMemoryException exception];
}
if (SecPKCS12Import((CFDataRef)data, (CFDictionaryRef)options,
&items) != noErr)
@throw [OFInvalidFormatException exception];
[(id)items autorelease];
for (NSDictionary *item in (NSArray *)items) {
bool hasIdentity = false;
SecCertificateRef cert;
NSArray *certs;
size_t i;
cert = (SecCertificateRef)
[item objectForKey: (NSString *)kSecImportItemIdentity];
if (cert != NULL) {
[chain addObject: [[[self alloc]
of_initWithCertificate: cert] autorelease]];
hasIdentity = true;
}
certs = [item objectForKey:
(NSString *)kSecImportItemCertChain];
if (certs == nil)
continue;
i = 0;
for (id cert_ in certs) {
cert = (SecCertificateRef)cert_;
if (hasIdentity && i == 0)
continue;
[chain addObject: [[[self alloc]
of_initWithCertificate: cert] autorelease]];
}
}
[chain makeImmutable];
objc_autoreleasePoolPop(pool);
return chain;
}
#endif
- (instancetype)of_initWithCertificate: (SecCertificateRef)certificate
#ifndef OF_IOS
privateKey: (SecKeychainItemRef)privateKey
keychain: (OFSecureTransportKeychain *)keychain
#endif
{
self = [super init];
_certificate = (SecCertificateRef)[(id)certificate retain];
#ifndef OF_IOS
if (privateKey != NULL)
_privateKey = (SecKeychainItemRef)[(id)privateKey retain];
_keychain = [keychain retain];
#endif
return self;
}
- (void)dealloc
{
[(id)_certificate release];
#ifndef OF_IOS
if (_privateKey != NULL)
[(id)_privateKey release];
[_keychain release];
#endif
[super dealloc];
}
@end