Last active
August 19, 2020 10:04
-
-
Save mittsh/09b6980be353166708965d231ccbc58e to your computer and use it in GitHub Desktop.
SecureIdentity.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void LMOpenSSL_Init(void) | |
{ | |
OpenSSL_add_all_algorithms(); | |
OpenSSL_add_all_ciphers(); | |
OpenSSL_add_all_digests(); | |
ERR_load_crypto_strings(); | |
} | |
void LMOpenSSL_ClearError(void) | |
{ | |
ERR_clear_error(); | |
} | |
dispatch_queue_t LMOpenSSL_GetQueue() | |
{ | |
static dispatch_once_t onceToken; | |
static dispatch_queue_t openSSLQueue; | |
dispatch_once(&onceToken, ^{ | |
openSSLQueue = dispatch_queue_create("com.luckymarmot.Paw.OpenSSLQueue", DISPATCH_QUEUE_SERIAL); | |
}); | |
return openSSLQueue; | |
} | |
#pragma mark - Create BIO | |
BIO* LMOpenSSL_CreateBIOWithData(CFDataRef data) | |
{ | |
if (data == NULL) { | |
return NULL; | |
} | |
BIO* bio = BIO_new(BIO_s_mem()); | |
BIO_write(bio, CFDataGetBytePtr(data), (int)CFDataGetLength(data)); | |
return bio; | |
} | |
#pragma mark - Create or Write BIO | |
CFDataRef LMOpenSSL_CreateDataFromBIO(BIO* bio) | |
{ | |
size_t len = BIO_pending(bio); | |
if (len == 0) { | |
return CFDataCreate(kCFAllocatorDefault, NULL, 0); | |
} | |
// get writing buffer | |
UInt8* buf = malloc(sizeof(UInt8) * len); | |
// copy to writing buffer | |
BIO_read(bio, buf, (int)len); | |
// creata data | |
CFDataRef data = CFDataCreate(kCFAllocatorDefault, buf, len); | |
// return | |
free(buf); | |
return data; | |
} | |
void LMOpenSSL_AppendBIO(BIO* bio, CFMutableDataRef mutableData) | |
{ | |
size_t len = BIO_pending(bio); | |
if (len > 0) { | |
// get current length and increase by `len` | |
CFIndex idx = CFDataGetLength(mutableData); | |
CFDataIncreaseLength(mutableData, len); | |
// get writing buffer | |
UInt8* buf = CFDataGetMutableBytePtr(mutableData) + idx; | |
// copy to writing buffer | |
BIO_read(bio, buf, (int)len); | |
} | |
} | |
#pragma mark - Read PEM | |
bool LMOpenSSL_PEM_ReadCertificates(CFDataRef inputPemData, X509** __cert, STACK_OF(X509)** __certStack) | |
{ | |
// get BIO (bytes IO) buffer | |
BIO* dataBio = LMOpenSSL_CreateBIOWithData(inputPemData); | |
if (dataBio == NULL) { | |
return false; | |
} | |
// parse X509 certificates | |
X509* x; | |
X509* cert = NULL; | |
STACK_OF(X509)* certStack = sk_X509_new_null(); | |
while (NULL != (x = PEM_read_bio_X509(dataBio, NULL, NULL, NULL))) { | |
if (cert == NULL) { | |
// certificate (main) | |
cert = x; | |
} | |
else { | |
// certificate chain (CA) | |
sk_X509_push(certStack, x); | |
} | |
} | |
if (__cert != NULL) { | |
*__cert = cert; | |
} | |
if (__certStack != NULL) { | |
*__certStack = certStack; | |
} | |
BIO_free(dataBio); | |
return (cert != NULL); | |
} | |
bool LMOpenSSL_PEM_ReadPrivateKey(CFDataRef inputPemData, EVP_PKEY** __pkey, CFStringRef password, LMOpenSSL_Error * __error) | |
{ | |
// get password data | |
char passwordBuf[2048]; | |
if (password != NULL) { | |
CFStringGetCString(password, passwordBuf, 2048, kCFStringEncodingUTF8); | |
} | |
// get BIO (bytes IO) buffer | |
BIO* dataBio = LMOpenSSL_CreateBIOWithData(inputPemData); | |
if (dataBio == NULL) { | |
if (__error != NULL) { | |
*__error = LMOpenSSL_UnknownError; | |
} | |
return false; | |
} | |
// parse Private Key | |
EVP_PKEY* pkey = PEM_read_bio_PrivateKey(dataBio, NULL, NULL, (password != NULL ? passwordBuf : "" /* pass empty string to prevent user prompt */)); | |
if (pkey == NULL) { | |
if (__error != NULL) { | |
unsigned long error = ERR_get_error(); | |
// printf("ERROR: lib = %d, reason = %d\n", ERR_GET_LIB(error), ERR_GET_REASON(error)); | |
if (ERR_R_PEM_LIB == ERR_GET_LIB(error) && ( | |
PEM_R_BAD_DECRYPT == ERR_GET_REASON(error) || | |
PEM_R_NO_START_LINE == ERR_GET_REASON(error))) { | |
*__error = LMOpenSSL_InvalidPassword; | |
} | |
else { | |
*__error = LMOpenSSL_UnknownError; | |
} | |
} | |
BIO_free(dataBio); | |
return false; | |
} | |
if (__pkey != NULL) { | |
*__pkey = pkey; | |
} | |
if (__error != NULL) { | |
*__error = LMOpenSSL_Success; | |
} | |
BIO_free(dataBio); | |
return true; | |
} | |
bool LMOpenSSL_PEM_Read(CFDataRef inputPemData, X509** __cert, STACK_OF(X509)** __certStack, EVP_PKEY** __pkey, CFStringRef inputPassword, LMOpenSSL_Error * __error) | |
{ | |
// read X509 certificate(s) | |
X509* cert; | |
STACK_OF(X509)* certStack; | |
if (!LMOpenSSL_PEM_ReadCertificates(inputPemData, &cert, &certStack)) { | |
if (__error != NULL) { | |
*__error = LMOpenSSL_MissingCertificateError; | |
} | |
return false; | |
} | |
// read private key | |
EVP_PKEY* pkey; | |
LMOpenSSL_Error pkeyError; | |
if (!LMOpenSSL_PEM_ReadPrivateKey(inputPemData, &pkey, inputPassword, &pkeyError)) { | |
if (__error != NULL) { | |
*__error = pkeyError; | |
} | |
X509_free(cert); | |
sk_X509_free(certStack); | |
return false; | |
} | |
// make sure the certificate matches the private key | |
if (1 != X509_check_private_key(cert, pkey)) { | |
if (__error != NULL) { | |
*__error = LMOpenSSL_CertificateDoesNotMatchPrivateKeyError; | |
} | |
X509_free(cert); | |
sk_X509_free(certStack); | |
EVP_PKEY_free(pkey); | |
return false; | |
} | |
// set return values | |
if (__cert != NULL) { | |
*__cert = cert; | |
} | |
if (__certStack != NULL) { | |
*__certStack = certStack; | |
} | |
if (__pkey != NULL) { | |
*__pkey = pkey; | |
} | |
if (__error != NULL) { | |
*__error = LMOpenSSL_Success; | |
} | |
return true; | |
} | |
#pragma mark - Write PEM | |
bool LMOpenSSL_PEM_AppendCertificate(X509* cert, CFMutableDataRef mutableData) | |
{ | |
BIO* bio = BIO_new(BIO_s_mem()); | |
if (1 != PEM_write_bio_X509(bio, cert)) { | |
BIO_free(bio); | |
return false; | |
} | |
LMOpenSSL_AppendBIO(bio, mutableData); | |
BIO_free(bio); | |
return true; | |
} | |
bool LMOpenSSL_PEM_AppendCertificateStack(STACK_OF(X509)* certStack, CFMutableDataRef mutableData) | |
{ | |
if (certStack == NULL || sk_X509_num(certStack) == 0) { | |
return true; | |
} | |
for (int k = 0; k < sk_X509_num(certStack); k++) { | |
X509* cert = sk_X509_value(certStack, k); | |
if (!LMOpenSSL_PEM_AppendCertificate(cert, mutableData)) { | |
return false; | |
} | |
} | |
return true; | |
} | |
bool LMOpenSSL_PEM_AppendPrivateKey(EVP_PKEY* pkey, CFMutableDataRef mutableData) | |
{ | |
BIO* bio = BIO_new(BIO_s_mem()); | |
if (1 != PEM_write_bio_PrivateKey(bio, pkey, NULL, NULL, 0, NULL, NULL)) { | |
BIO_free(bio); | |
return false; | |
} | |
LMOpenSSL_AppendBIO(bio, mutableData); | |
BIO_free(bio); | |
return true; | |
} | |
CFDataRef LMOpenSSL_PEM_CreateFullChain(X509* cert, STACK_OF(X509)* certStack, EVP_PKEY* pkey) | |
{ | |
CFMutableDataRef mutableData = CFDataCreateMutable(kCFAllocatorDefault, 0); | |
// output certificate | |
if (cert != NULL) { | |
if (!LMOpenSSL_PEM_AppendCertificate(cert, mutableData)) { | |
CFRelease(mutableData); | |
return NULL; | |
} | |
} | |
// output certificate chain (other certificates) | |
if (certStack != NULL) { | |
if (!LMOpenSSL_PEM_AppendCertificateStack(certStack, mutableData)) { | |
CFRelease(mutableData); | |
return NULL; | |
} | |
} | |
// output PEM private key | |
if (pkey != NULL) { | |
if (!LMOpenSSL_PEM_AppendPrivateKey(pkey, mutableData)) { | |
CFRelease(mutableData); | |
return NULL; | |
} | |
} | |
// copy immutable | |
CFDataRef pemData = CFDataCreateCopy(kCFAllocatorDefault, mutableData); | |
// free | |
CFRelease(mutableData); | |
return pemData; | |
} | |
#pragma mark - Convert to PEM | |
CFDataRef LMCreatePEMDataFromPEM(CFDataRef inputPemData, CFStringRef inputPassword, LMOpenSSL_Error* __error) | |
{ | |
// read PEM | |
X509* cert; | |
STACK_OF(X509)* certStack; | |
EVP_PKEY* pkey; | |
if (!LMOpenSSL_PEM_Read(inputPemData, &cert, &certStack, &pkey, inputPassword, __error)) { | |
return NULL; | |
} | |
// output PEM | |
CFDataRef pemData = LMOpenSSL_PEM_CreateFullChain(cert, certStack, pkey); | |
if (__error != NULL) { | |
*__error = LMOpenSSL_Success; | |
} | |
// free | |
X509_free(cert); | |
sk_X509_free(certStack); | |
EVP_PKEY_free(pkey); | |
return pemData; | |
} | |
CFDataRef LMCreatePEMDataFromPKCS12Archive(CFDataRef pkcs12Data, CFStringRef password, LMOpenSSL_Error * __error) | |
{ | |
// get password data | |
char passwordBuf[2048]; | |
if (password != NULL) { | |
CFStringGetCString(password, passwordBuf, 2048, kCFStringEncodingUTF8); | |
} | |
// get BIO (bytes IO) buffer | |
BIO* dataBio = LMOpenSSL_CreateBIOWithData(pkcs12Data); | |
if (dataBio == NULL) { | |
if (__error != NULL) { | |
*__error = LMOpenSSL_UnknownError; | |
} | |
return NULL; | |
} | |
// get PKCS12 object | |
PKCS12* p12 = d2i_PKCS12_bio(dataBio, NULL); | |
if (p12 == NULL) { | |
if (__error != NULL) { | |
*__error = LMOpenSSL_InvalidPKCS12Archive; | |
} | |
BIO_free(dataBio); | |
return NULL; | |
} | |
// parse PKCS12 object | |
EVP_PKEY* pkey; | |
X509* cert; | |
STACK_OF(X509)* certStack = NULL; | |
if (PKCS12_OK != PKCS12_parse(p12, (password == NULL ? NULL : passwordBuf), &pkey, &cert, &certStack /* additional certificates (chain) */)) { | |
if (__error != NULL) { | |
unsigned long error = ERR_get_error(); | |
if (ERR_LIB_PKCS12 == ERR_GET_LIB(error) && | |
PKCS12_R_MAC_VERIFY_FAILURE == ERR_GET_REASON(error)) { | |
*__error = LMOpenSSL_InvalidPassword; | |
} | |
else { | |
*__error = LMOpenSSL_InvalidPKCS12Archive; | |
} | |
} | |
PKCS12_free(p12); | |
BIO_free(dataBio); | |
return NULL; | |
} | |
// output PEM | |
CFDataRef pemData = LMOpenSSL_PEM_CreateFullChain(cert, certStack, pkey); | |
if (__error != NULL) { | |
*__error = LMOpenSSL_Success; | |
} | |
// free | |
PKCS12_free(p12); | |
BIO_free(dataBio); | |
sk_X509_free(certStack); | |
EVP_PKEY_free(pkey); | |
return pemData; | |
} | |
#pragma mark - Convert to PKCS12 | |
CFDataRef LMCreatePKCS12ArchiveDataFromPEM(CFDataRef inputPemData, CFStringRef inputPassword, CFStringRef outputPassword, LMOpenSSL_Error * __error) | |
{ | |
// read PEM | |
X509* cert; | |
STACK_OF(X509)* certStack; | |
EVP_PKEY* pkey; | |
if (!LMOpenSSL_PEM_Read(inputPemData, &cert, &certStack, &pkey, inputPassword, __error)) { | |
return NULL; | |
} | |
// get ouput password data | |
char outputPasswordBuf[2048]; | |
CFStringGetCString(outputPassword, outputPasswordBuf, 2048, kCFStringEncodingUTF8); | |
// create PKCS12 object | |
PKCS12* p12 = PKCS12_create(outputPasswordBuf, "", pkey, cert, certStack, 0, 0, 0, 0, 0); | |
if (p12 == NULL) { | |
if (__error != NULL) { | |
*__error = LMOpenSSL_UnknownError; | |
} | |
PKCS12_free(p12); | |
sk_X509_free(certStack); | |
EVP_PKEY_free(pkey); | |
return NULL; | |
} | |
// get data from PKCS12 object | |
BIO* bio = BIO_new(BIO_s_mem()); | |
i2d_PKCS12_bio(bio, p12); | |
CFDataRef p12Data = LMOpenSSL_CreateDataFromBIO(bio); | |
// return | |
BIO_free(bio); | |
PKCS12_free(p12); | |
sk_X509_free(certStack); | |
EVP_PKEY_free(pkey); | |
return p12Data; | |
} | |
#pragma mark - Extract data from PEM | |
CFStringRef LMCreateSubjectNameStringWithPEMData(CFDataRef pemData) | |
{ | |
// if NULL or empty, return NULL | |
if (pemData == NULL || CFDataGetLength(pemData) == 0) { | |
return NULL; | |
} | |
// get X509 | |
BIO* certBio = LMOpenSSL_CreateBIOWithData(pemData); | |
X509* certX509 = PEM_read_bio_X509(certBio, NULL, NULL, NULL); | |
if (certX509 == NULL) { | |
BIO_free(certBio); | |
return NULL; | |
} | |
// get subject | |
X509_NAME* subjectName = X509_get_subject_name(certX509); | |
if (subjectName == NULL) { | |
BIO_free(certBio); | |
return NULL; | |
} | |
// get subject as string | |
char subjectCString[2048]; | |
X509_NAME_oneline(subjectName, subjectCString, 2048); | |
CFStringRef subjectString = CFStringCreateWithCString(kCFAllocatorDefault, subjectCString, kCFStringEncodingUTF8); | |
BIO_free(certBio); | |
X509_free(certX509); | |
return subjectString; | |
} | |
CFDataRef LMCreateDigestDataWithPEMData(CFDataRef pemData, CFStringRef digestName) | |
{ | |
// if NULL or empty, return NULL | |
if (pemData == NULL || CFDataGetLength(pemData) == 0) { | |
return NULL; | |
} | |
// get X509 | |
BIO* certBio = LMOpenSSL_CreateBIOWithData(pemData); | |
X509* certX509 = PEM_read_bio_X509(certBio, NULL, NULL, NULL); | |
if (certX509 == NULL) { | |
BIO_free(certBio); | |
return NULL; | |
} | |
// get digest type | |
char digestNameBuf[2048]; | |
CFStringGetCString(digestName, digestNameBuf, 2048, kCFStringEncodingUTF8); | |
const EVP_MD* digestType = EVP_get_digestbyname(digestNameBuf); | |
if (digestType == NULL) { | |
BIO_free(certBio); | |
X509_free(certX509); | |
return NULL; | |
} | |
// get digest buffer | |
unsigned char buf[EVP_MAX_MD_SIZE]; | |
unsigned int len; | |
if (0 == X509_digest(certX509, digestType, buf, &len)) { | |
BIO_free(certBio); | |
X509_free(certX509); | |
return NULL; | |
} | |
// get CFData and return | |
CFDataRef digestData = CFDataCreate(kCFAllocatorDefault, buf, len); | |
BIO_free(certBio); | |
X509_free(certX509); | |
return digestData; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
OSStatus LMSecCertificateQueryByName(CFStringRef certificateName, SecCertificateRef* __certificate) | |
{ | |
const void* keys[] = { kSecClass, kSecAttrLabel, kSecReturnRef }; | |
const void* values[] = { kSecClassCertificate, certificateName, kCFBooleanTrue }; | |
CFDictionaryRef query = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 3, NULL, NULL); | |
CFTypeRef certificate = NULL; | |
OSStatus err = SecItemCopyMatching(query, &certificate); | |
if (noErr != err) { | |
CFRelease(query); | |
return err; | |
} | |
if (__certificate != NULL) { | |
*__certificate = (SecCertificateRef)certificate; | |
} | |
CFRelease(query); | |
return noErr; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
OSStatus LMSecIdentityCreateWithCertificate(SecCertificateRef certificate, SecIdentityRef* __identity) | |
{ | |
OSStatus err = noErr; | |
SecIdentityRef identity = NULL; | |
err = SecIdentityCreateWithCertificate(NULL /* search in all Keychains */, certificate, &identity); | |
if (noErr != err) { | |
return err; | |
} | |
if (__identity != NULL) { | |
*__identity = identity; | |
} | |
return noErr; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CFStringRef LMCreateSecKeychainDefaultPath() | |
{ | |
CFStringRef appSupportPath = LMCreateApplicationSupportDirectoryPathString(); | |
CFStringRef keychainPath = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@/Paw.keychain"), appSupportPath); | |
CFRelease(appSupportPath); | |
return keychainPath; | |
} | |
OSStatus LMSecKeychainGetPawDefault(SecKeychainRef * __pawDefaultKeychain) | |
{ | |
OSStatus status = noErr; | |
// get path to keychain | |
CFStringRef keychainPath = LMCreateSecKeychainDefaultPath(); | |
char keychainPathBuf[10240]; | |
CFStringGetCString(keychainPath, keychainPathBuf, 10240, kCFStringEncodingUTF8); | |
CFRelease(keychainPath); | |
// open keychain | |
SecKeychainRef keychain; | |
if (noErr != (status = SecKeychainOpen(keychainPathBuf, &keychain))) { | |
printf("ERROR: cannot open keychain = %d\n", status); | |
return status; | |
} | |
// get status and create if needed | |
SecKeychainStatus keychainStatus; | |
if (noErr != (status = SecKeychainGetStatus(keychain, &keychainStatus))) { | |
printf("ERROR: cannot get keychain status = %d\n", status); | |
if (noErr != (status = SecKeychainCreate(keychainPathBuf, 3, "paw", false, NULL, &keychain))) { | |
printf("ERROR: cannot create new keychain = %d\n", status); | |
if (noErr != (status = SecKeychainGetStatus(keychain, &keychainStatus))) { | |
printf("ERROR: cannot get (new) keychain status = %d\n", status); | |
return status; | |
} | |
} | |
} | |
// unlock if needed | |
if (0 == (keychainStatus & kSecUnlockStateStatus)) { | |
SecKeychainUnlock(keychain, 3, "paw", true); | |
} | |
// assign return value | |
if (NULL != __pawDefaultKeychain) { | |
*__pawDefaultKeychain = keychain; | |
} | |
return noErr; | |
} | |
OSStatus LMSecIdentityFromP12(CFDataRef p12Data, CFStringRef p12Passphrase, SecIdentityRef* __identity, CFArrayRef* __certChain) | |
{ | |
OSStatus status = noErr; | |
// get Paw default keychain | |
SecKeychainRef pawDefaultKeychain; | |
if (noErr != (status = LMSecKeychainGetPawDefault(&pawDefaultKeychain))) { | |
printf("ERROR: cannot get Paw default keychain, status = %d\n", status); | |
return status; | |
} | |
// options | |
const void* keys[] = { kSecImportExportPassphrase, kSecImportExportKeychain }; | |
const void* values[] = { p12Passphrase, pawDefaultKeychain }; | |
CFDictionaryRef options = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 2, NULL, NULL); | |
// import p12 | |
CFArrayRef items; | |
if (errSecSuccess != (status = SecPKCS12Import(p12Data, options, &items)) || CFArrayGetCount(items) == 0) { | |
printf("ERROR: could not import items from P12 file, status = %d\n", status); | |
CFRelease(options); | |
CFRelease(pawDefaultKeychain); | |
return status; | |
} | |
// get identity | |
CFDictionaryRef itemDict = CFArrayGetValueAtIndex(items, 0); | |
SecIdentityRef identity = (SecIdentityRef)CFDictionaryGetValue(itemDict, kSecImportItemIdentity); | |
if (__identity != NULL) { | |
CFRetain(identity); | |
*__identity = identity; | |
} | |
CFArrayRef certChain = (CFArrayRef)CFDictionaryGetValue(itemDict, kSecImportItemCertChain); | |
if (__certChain != NULL) { | |
CFRetain(certChain); | |
*__certChain = certChain; | |
} | |
CFRelease(options); | |
CFRelease(pawDefaultKeychain); | |
return noErr; | |
} | |
OSStatus LMSecCertificateQueryByName(CFStringRef certificateName, SecCertificateRef* __certificate) | |
{ | |
const void* keys[] = { kSecClass, kSecAttrLabel, kSecReturnRef }; | |
const void* values[] = { kSecClassCertificate, certificateName, kCFBooleanTrue }; | |
CFDictionaryRef query = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 3, NULL, NULL); | |
CFTypeRef certificate = NULL; | |
OSStatus err = SecItemCopyMatching(query, &certificate); | |
if (noErr != err) { | |
CFRelease(query); | |
return err; | |
} | |
if (__certificate != NULL) { | |
*__certificate = (SecCertificateRef)certificate; | |
} | |
CFRelease(query); | |
return noErr; | |
} | |
OSStatus LMSecIdentityCreateWithCertificate(SecCertificateRef certificate, SecIdentityRef* __identity) | |
{ | |
OSStatus err = noErr; | |
SecIdentityRef identity = NULL; | |
err = SecIdentityCreateWithCertificate(NULL /* search in all Keychains */, certificate, &identity); | |
if (noErr != err) { | |
return err; | |
} | |
if (__identity != NULL) { | |
*__identity = identity; | |
} | |
return noErr; | |
} | |
OSStatus LMSecIdentityGetAll(CFArrayRef * __identities) | |
{ | |
// build query | |
const void* keys[] = { kSecClass, kSecMatchLimit, kSecReturnRef /* do not specify kSecMatchSearchList to search in all Keychains */ }; | |
const void* values[] = { kSecClassIdentity, kSecMatchLimitAll, kCFBooleanTrue }; | |
CFDictionaryRef query = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 3, NULL, NULL); | |
// get all SecIdentity items | |
CFArrayRef identities = NULL; | |
OSStatus status = SecItemCopyMatching(query, (CFTypeRef*)&identities); | |
if (status != noErr) { | |
printf("ERROR: cannot list identities Keychain (err=%d)", status); | |
CFRelease(query); | |
return status; | |
} | |
// set result | |
if (__identities != NULL) { | |
*__identities = identities; | |
} | |
CFRelease(query); | |
return noErr; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@objc(LMSecureIdentity) | |
protocol SecureIdentity { | |
@objc var identity: SecIdentity { get } | |
@objc var urlCredential: URLCredential? { get } | |
@objc var objc_mainCertificate: SecCertificate? { get } | |
@objc var objc_commonName: String? { get } | |
@objc var objc_certificateChain: CFArray { get } | |
} | |
@objc(LMKeychainSecureIdentity) | |
class KeychainSecureIdentity : NSObject, SecureIdentity { | |
@objc private(set) public var identity: SecIdentity | |
@objc private(set) public var keychainCertificateName: String | |
@objc private(set) public var certificate: SecCertificate | |
@objc public init(keychainCertificateName: String) throws { | |
self.keychainCertificateName = keychainCertificateName | |
certificate = try KeychainSecureIdentity.getCertificate(keychainCertificateName: keychainCertificateName) | |
identity = try KeychainSecureIdentity.getIdentity(certificate: certificate) | |
super.init() | |
} | |
var certificateChain: [SecCertificate] { | |
return [certificate] | |
} | |
var urlCredential: URLCredential? { | |
return URLCredential(identity: identity, certificates: certificateChain, persistence: .none) | |
} | |
// MARK: Static | |
static public func getCertificate(keychainCertificateName: String) throws -> SecCertificate { | |
var certificate: SecCertificate? | |
guard noErr == LMSecCertificateQueryByName(keychainCertificateName as NSString, &certificate) else { | |
throw SecureIdentityError.keychainCertificateNotFound | |
} | |
return certificate! | |
} | |
static public func getIdentity(certificate: SecCertificate) throws -> SecIdentity { | |
var identity: SecIdentity? | |
guard noErr == LMSecIdentityCreateWithCertificate(certificate, &identity) else { | |
throw SecureIdentityError.keychainMissingPrivateKey | |
} | |
return identity! | |
} | |
} | |
@objc(LMPEMSecureIdentity) | |
class PEMSecureIdentity : NSObject, SecureIdentity { | |
@objc private(set) public var pem: Data | |
@objc private(set) public var identity: SecIdentity | |
private(set) public var certificateChain: [SecCertificate] | |
@objc public init(pem: Data, password: String? = nil) throws { | |
var openSSLError: LMOpenSSL_Error = LMOpenSSL_Success | |
var _pemData: Data? | |
LMOpenSSL_GetQueue().sync { | |
_pemData = LMCreatePEMDataFromPEM(pem as NSData, password as NSString?, &openSSLError)?.takeRetainedValue() as Data? | |
LMOpenSSL_ClearError() | |
} | |
guard let pemData = _pemData else { | |
switch openSSLError { | |
case LMOpenSSL_InvalidPassword: | |
if password != nil && !password!.isEmpty { | |
throw SecureIdentityError.pemPasswordInvalid | |
} else { | |
throw SecureIdentityError.pemPasswordRequired | |
} | |
case LMOpenSSL_MissingCertificateError: | |
throw SecureIdentityError.pemMissingCertificateError | |
case LMOpenSSL_CertificateDoesNotMatchPrivateKeyError: | |
throw SecureIdentityError.pemCertificateDoesNotMatchPrivateKeyError | |
default: | |
throw SecureIdentityError.pemArchiveParsingError | |
} | |
} | |
self.pem = pemData | |
(identity, certificateChain) = try PEMSecureIdentity.getIdentity(pem: pemData) | |
super.init() | |
} | |
@objc public init(pkcs12Archive: Data, password: String? = nil) throws { | |
var openSSLError: LMOpenSSL_Error = LMOpenSSL_Success | |
var _pemData: Data? | |
LMOpenSSL_GetQueue().sync { | |
_pemData = LMCreatePEMDataFromPKCS12Archive(pkcs12Archive as NSData, password as NSString?, &openSSLError)?.takeRetainedValue() as Data? | |
LMOpenSSL_ClearError() | |
} | |
guard let pemData = _pemData else { | |
switch openSSLError { | |
case LMOpenSSL_InvalidPassword: | |
if password != nil && !password!.isEmpty { | |
throw SecureIdentityError.pkcs12ArchivePasswordInvalid | |
} else { | |
throw SecureIdentityError.pkcs12ArchivePasswordRequired | |
} | |
default: | |
throw SecureIdentityError.pkcs12ArchiveParsingError | |
} | |
} | |
self.pem = pemData | |
(identity, certificateChain) = try PEMSecureIdentity.getIdentity(pem: pemData) | |
super.init() | |
} | |
var urlCredential: URLCredential? { | |
return URLCredential(identity: identity, certificates: certificateChain, persistence: .none) | |
} | |
@objc public func getFingerprint(algorithm: SecureIdentityFingerprintAlgorithm) -> String? { | |
var _fingerprintData: Data? | |
LMOpenSSL_GetQueue().sync { | |
_fingerprintData = LMCreateDigestDataWithPEMData(pem as NSData, algorithm.openSSLName as NSString)?.takeRetainedValue() as Data? | |
LMOpenSSL_ClearError() | |
} | |
guard let fingerprintData = _fingerprintData else { | |
return nil | |
} | |
return (fingerprintData as NSData).fingerprintHexString | |
} | |
// MARK: Static | |
static func pkcs12Data(pem: Data, outputPassword: String) throws -> Data { | |
var _pkcs12Data: Data? | |
LMOpenSSL_GetQueue().sync { | |
_pkcs12Data = LMCreatePKCS12ArchiveDataFromPEM(pem as NSData, nil, outputPassword as NSString, nil)?.takeRetainedValue() as Data? | |
LMOpenSSL_ClearError() | |
} | |
guard let pkcs12Data = _pkcs12Data else { | |
throw SecureIdentityError.pemUnknownError | |
} | |
return pkcs12Data | |
} | |
static func getIdentity(pem: Data) throws -> (SecIdentity, [SecCertificate]) { | |
return try self.getIdentity(pkcs12Data: self.pkcs12Data(pem: pem, outputPassword: "paw"), password: "paw") | |
} | |
static func getIdentity(pkcs12Data: Data, password: String) throws -> (SecIdentity, [SecCertificate]) { | |
var identity: SecIdentity? | |
var certChain: CFArray? | |
let err = LMSecIdentityFromP12(pkcs12Data as NSData, password as NSString, &identity, &certChain) | |
guard noErr == err else { | |
if err == errSecPassphraseRequired { | |
throw SecureIdentityError.pkcs12ArchivePasswordRequired | |
} | |
else if err == errSecInvalidPasswordRef { | |
throw SecureIdentityError.pkcs12ArchivePasswordInvalid | |
} | |
throw SecureIdentityError.pkcs12ArchiveParsingError | |
} | |
return (identity!, certChain as! [SecCertificate]) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment