X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/c38e3ce98599a410a47dc10253faa4d5830f13b2..427c49bcad63d042b29ada2ac27e3dfc4845c779:/sec/Security/SecOTRFullIdentity.c diff --git a/sec/Security/SecOTRFullIdentity.c b/sec/Security/SecOTRFullIdentity.c new file mode 100644 index 00000000..2bb032fb --- /dev/null +++ b/sec/Security/SecOTRFullIdentity.c @@ -0,0 +1,509 @@ +/* + * SecOTRFullIdentity.c + * libsecurity_libSecOTR + * + * Created by Mitch Adler on 2/9/11. + * Copyright 2011 Apple Inc. All rights reserved. + * + */ + +#include "SecOTR.h" +#include "SecOTRIdentityPriv.h" +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include "SecOTRErrors.h" + +#include + +// +// Algorthim ID initialization +// + +#define kMessageIdentityRSAKeyBits 1280 +#define kMessageIdentityECKeyBits 256 + +void EnsureOTRAlgIDInited(void) +{ + static dispatch_once_t kSignatureAlgID_ONCE; + static SecAsn1AlgId kOTRECSignatureAlgID; + + dispatch_once(&kSignatureAlgID_ONCE, ^{ + kOTRECSignatureAlgID.algorithm = CSSMOID_ECDSA_WithSHA1; + kOTRSignatureAlgIDPtr = &kOTRECSignatureAlgID; + }); +} + + +static CFStringRef sSigningKeyName = CFSTR("OTR Signing Key"); + +// +// SecOTRFullIdentity implementation +// + +CFGiblisFor(SecOTRFullIdentity); + +static CF_RETURNS_RETAINED CFStringRef SecOTRFullIdentityCopyDescription(CFTypeRef cf) { + SecOTRFullIdentityRef requestor = (SecOTRFullIdentityRef)cf; + return CFStringCreateWithFormat(kCFAllocatorDefault,NULL,CFSTR(""), + requestor, + requestor->publicIDHash[0], requestor->publicIDHash[1], + requestor->publicIDHash[2], requestor->publicIDHash[3], + requestor->publicIDHash[4], requestor->publicIDHash[5], + requestor->publicIDHash[6], requestor->publicIDHash[7]); +} + +static void SecOTRFullIdentityDestroy(CFTypeRef cf) { + SecOTRFullIdentityRef requestor = (SecOTRFullIdentityRef)cf; + + CFReleaseNull(requestor->privateSigningKey); + CFReleaseNull(requestor->publicSigningKey); +} + + +// +// Shared statics +// + +static OSStatus SecOTRFIPurgeFromKeychainByValue(SecKeyRef key, CFStringRef label) +{ + OSStatus status; + const void *keys[] = { kSecClass, + kSecAttrLabel, + kSecValueRef, + }; + const void *values[] = { kSecClassKey, + label, + key, + }; + CFDictionaryRef dict = CFDictionaryCreate(NULL, keys, values, array_size(values), NULL, NULL); + status = SecItemDelete(dict); + CFReleaseSafe(dict); + + return status; +} + +static bool SecKeyDigestAndSignWithError( + SecKeyRef key, /* Private key */ + const SecAsn1AlgId *algId, /* algorithm oid/params */ + const uint8_t *dataToDigest, /* signature over this data */ + size_t dataToDigestLen,/* length of dataToDigest */ + uint8_t *sig, /* signature, RETURNED */ + size_t *sigLen, /* IN/OUT */ + CFErrorRef *error) { + + OSStatus status = SecKeyDigestAndSign(key, algId, dataToDigest, dataToDigestLen, sig, sigLen); + require_noerr(status, fail); + return true; +fail: + SecOTRCreateError(secOTRErrorOSError, status, CFSTR("Error signing message. OSStatus in error code."), NULL, error); + return false; +} + +// +// SecOTRFullIdentity Functions +// + +static bool SecOTRFICachePublicHash(SecOTRFullIdentityRef fullID, CFErrorRef *error) +{ + SecOTRPublicIdentityRef pubID = SecOTRPublicIdentityCopyFromPrivate(NULL, fullID, error); + + require(pubID, fail); + + SecOTRPICopyHash(pubID, fullID->publicIDHash); + +fail: + CFReleaseSafe(pubID); + return (pubID != NULL); // This is safe because we're not accessing the value after release, just checking if it ever had a value of some nature. +} + +#if !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR +#define SEC_CONST_DECL(k,v) CFTypeRef k = (CFTypeRef)(CFSTR(v)); +SEC_CONST_DECL (kSecAttrAccessible, "pdmn"); +SEC_CONST_DECL (kSecAttrAccessibleAlwaysThisDeviceOnly, "dku"); +#endif + +SecOTRFullIdentityRef SecOTRFullIdentityCreate(CFAllocatorRef allocator, CFErrorRef *error) +{ + CFDictionaryRef keygen_parameters = NULL; + SecOTRFullIdentityRef newID = CFTypeAllocate(SecOTRFullIdentity, struct _SecOTRFullIdentity, allocator); + SecKeyRef tempSigningKey = NULL; + + newID->publicSigningKey = NULL; + newID->privateSigningKey = NULL; + + require(newID, out); + + EnsureOTRAlgIDInited(); + + const int signing_keySizeLocal = kMessageIdentityECKeyBits; + CFNumberRef signing_bitsize = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &signing_keySizeLocal); + + const void *signing_keygen_keys[] = { kSecAttrKeyType, + kSecAttrKeySizeInBits, + kSecAttrIsPermanent, + kSecAttrAccessible, + kSecAttrLabel, + }; + + const void *signing_keygen_vals[] = { kSecAttrKeyTypeEC, + signing_bitsize, + kCFBooleanTrue, + kSecAttrAccessibleAlwaysThisDeviceOnly, + sSigningKeyName + }; + keygen_parameters = CFDictionaryCreate(kCFAllocatorDefault, + signing_keygen_keys, signing_keygen_vals, array_size(signing_keygen_vals), + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks ); + CFReleaseNull(signing_bitsize); + require_noerr(SecKeyGeneratePair(keygen_parameters, &tempSigningKey, &newID->privateSigningKey), out); + CFReleaseNull(keygen_parameters); + + newID->publicSigningKey = SecKeyCreatePublicFromPrivate(tempSigningKey); + + (void) SecOTRFIPurgeFromKeychainByValue(tempSigningKey, sSigningKeyName); + CFReleaseNull(tempSigningKey); + + require(SecOTRFICachePublicHash(newID, error), out); + + return newID; + +out: + if (NULL != newID) { + SecOTRFIPurgeFromKeychain(newID, NULL); + } + CFReleaseSafe(keygen_parameters); + CFReleaseSafe(newID); + CFReleaseSafe(tempSigningKey); + return NULL; +} + + +static +OSStatus SecOTRFICreatePrivateKeyReadPersistentRef(const uint8_t **bytes, size_t *size, SecKeyRef* privateKey) +{ + OSStatus status = errSecParam; + uint16_t dataSize; + CFDataRef persistentRef = NULL; + + require_noerr_quiet(readSize(bytes, size, &dataSize), fail); + require_quiet(dataSize <= *size, fail); + + SecKeyRef lookedUpKey = NULL; + persistentRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, *bytes, dataSize, kCFAllocatorNull); + require_quiet(persistentRef, fail); + + require_noerr_quiet(SecKeyFindWithPersistentRef(persistentRef, &lookedUpKey), fail); + + *privateKey = lookedUpKey; + + *bytes += dataSize; + *size -= dataSize; + + status = errSecSuccess; + +fail: + CFReleaseSafe(persistentRef); + + return status; +} + +static +OSStatus SecOTRFICreateKeysFromReadPersistentRef(const uint8_t **bytes, size_t *size, SecKeyRef *publicKey, SecKeyRef* privateKey) +{ + SecKeyRef foundKey = NULL; + + OSStatus status = SecOTRFICreatePrivateKeyReadPersistentRef(bytes, size, &foundKey); + require_noerr_quiet(status, fail); + require_quiet(foundKey, fail); + + *publicKey = SecKeyCreatePublicFromPrivate(*privateKey); + require_action_quiet(*publicKey, fail, status = errSecParam); + + *privateKey = foundKey; + foundKey = NULL; + + status = errSecSuccess; + +fail: + CFReleaseSafe(foundKey); + return status; +} + +typedef SecKeyRef (*SecOTRPublicKeyCreateFunction)(CFAllocatorRef allocator, const uint8_t** data, size_t* limit); + +static +OSStatus SecOTRFICreateKeysFromReadPersistentRefAndPublicKey(const uint8_t **bytes, size_t *size, SecKeyRef *publicKey, SecKeyRef* privateKey, SecOTRPublicKeyCreateFunction createPublic) +{ + SecKeyRef foundKey = NULL; + + OSStatus status = SecOTRFICreatePrivateKeyReadPersistentRef(bytes, size, &foundKey); + require_noerr_quiet(status, fail); + require_quiet(foundKey, fail); + + *publicKey = (*createPublic)(NULL, bytes, size); + require_action_quiet(*publicKey, fail, status = errSecParam); + + *privateKey = foundKey; + +fail: + return status; +} + +static +OSStatus SecOTRFIInitFromV1Bytes(SecOTRFullIdentityRef newID, CFAllocatorRef allocator, + const uint8_t **bytes,size_t *size) { + require(**bytes == 1, fail); + ++*bytes; + --*size; + + require_noerr_quiet(SecOTRFICreateKeysFromReadPersistentRef(bytes, size, &newID->publicSigningKey, &newID->privateSigningKey), fail); + + return errSecSuccess; + +fail: + CFReleaseNull(newID->publicSigningKey); + CFReleaseNull(newID->privateSigningKey); + + return errSecParam; +} + +static +OSStatus SecOTRFIInitFromV2Bytes(SecOTRFullIdentityRef newID, CFAllocatorRef allocator, + const uint8_t **bytes,size_t *size) { + require(**bytes == 2, fail); + ++*bytes; + --*size; + + require_noerr_quiet(SecOTRFICreateKeysFromReadPersistentRefAndPublicKey(bytes, size, &newID->publicSigningKey, &newID->privateSigningKey, &CreateECPublicKeyFrom), fail); + + return errSecSuccess; + +fail: + CFReleaseNull(newID->publicSigningKey); + CFReleaseNull(newID->privateSigningKey); + + return errSecParam; +} + +SecOTRFullIdentityRef SecOTRFullIdentityCreateFromSecKeyRef(CFAllocatorRef allocator, SecKeyRef privateKey, + CFErrorRef *error) { + // TODO - make sure this is an appropriate key type + SecOTRFullIdentityRef newID = CFTypeAllocate(SecOTRFullIdentity, struct _SecOTRFullIdentity, allocator); + newID->privateSigningKey = privateKey; + CFRetain(newID->privateSigningKey); + newID->publicSigningKey = SecKeyCreatePublicFromPrivate(privateKey); + require(SecOTRFICachePublicHash(newID, error), fail); + return newID; +fail: + CFRelease(newID->privateSigningKey); + CFRelease(newID->publicSigningKey); + CFReleaseSafe(newID); + return NULL; +} + +SecOTRFullIdentityRef SecOTRFullIdentityCreateFromBytes(CFAllocatorRef allocator, const uint8_t**bytes, size_t *size, CFErrorRef *error) +{ + SecOTRFullIdentityRef newID = CFTypeAllocate(SecOTRFullIdentity, struct _SecOTRFullIdentity, allocator); + EnsureOTRAlgIDInited(); + + require(*size > 0, fail); + + switch (**bytes) { + case 1: + require_noerr_quiet(SecOTRFIInitFromV1Bytes(newID, allocator, bytes, size), fail); + break; + case 2: + require_noerr_quiet(SecOTRFIInitFromV2Bytes(newID, allocator, bytes, size), fail); + break; + case 0: // Version 0 was used in seeds of 5.0, transition from those seeds unsupported - keys were in exported data. + default: + require(false, fail); + break; + } + + require(SecOTRFICachePublicHash(newID, error), fail); + + return newID; + +fail: + if (NULL != newID) { + SecOTRFIPurgeFromKeychain(newID, NULL); + } + CFReleaseSafe(newID); + return NULL; +} + +SecOTRFullIdentityRef SecOTRFullIdentityCreateFromData(CFAllocatorRef allocator, CFDataRef data, CFErrorRef *error) +{ + if (data == NULL) + return NULL; + + size_t size = (size_t)CFDataGetLength(data); + const uint8_t* bytes = CFDataGetBytePtr(data); + + return SecOTRFullIdentityCreateFromBytes(allocator, &bytes, &size, error); +} + +bool SecOTRFIPurgeFromKeychain(SecOTRFullIdentityRef thisID, CFErrorRef *error) +{ + OSStatus result = SecOTRFIPurgeFromKeychainByValue(thisID->privateSigningKey, sSigningKeyName); + if (errSecSuccess == result) { + return true; + } else { + SecOTRCreateError(secOTRErrorOSError, result, CFSTR("OSStatus returned in error code"), NULL, error); + return false; + } +} + + +static OSStatus SecOTRFIPurgeAllFromKeychainByLabel(CFStringRef label) +{ + OSStatus status; + const void *keys[] = { kSecClass, + kSecAttrKeyClass, + kSecAttrLabel, + }; + const void *values[] = { kSecClassKey, + kSecAttrKeyClassPrivate, + label, + }; + CFDictionaryRef dict = CFDictionaryCreate(NULL, keys, values, array_size(values), NULL, NULL); + bool deleteAtLeastOne = false; + int loopLimiter = 500; + do { + status = SecItemDelete(dict); + if (status == errSecSuccess) { + deleteAtLeastOne = true; + } + loopLimiter--; + } while ((status == errSecSuccess) && (loopLimiter > 0)); + if ((status == errSecItemNotFound) && (deleteAtLeastOne)) { + // We've looped until we can't delete any more keys. + // Since this will produce an expected 'itemNotFound', but we don't want to break the contract above + // (and also we want to make sense) + // we muffle the non-error to a success case, which it is. + status = errSecSuccess; + } + CFReleaseSafe(dict); + + return status; +} + +bool SecOTRFIPurgeAllFromKeychain(CFErrorRef *error) +{ + OSStatus result = SecOTRFIPurgeAllFromKeychainByLabel(sSigningKeyName); + if (errSecSuccess == result) { + return true; + } else { + SecOTRCreateError(secOTRErrorOSError, result, CFSTR("OSStatus returned in error code"), NULL, error); + return false; + } +} + +static OSStatus appendPersistentRefData(SecKeyRef theKey, CFMutableDataRef serializeInto, CFStringRef name) +{ + OSStatus status; + CFDataRef persistent_ref = NULL; + require_noerr(status = SecKeyCopyPersistentRef(theKey, &persistent_ref), fail); + + status = appendSizeAndData(persistent_ref, serializeInto); + +fail: + CFReleaseSafe(persistent_ref); + + return status; +} + +static OSStatus SecOTRFIAppendV2Serialization(SecOTRFullIdentityRef fullID, CFMutableDataRef serializeInto) +{ + const uint8_t version = 2; + CFIndex start = CFDataGetLength(serializeInto); + + CFDataAppendBytes(serializeInto, &version, sizeof(version)); + + require(errSecSuccess == appendPersistentRefData(fullID->privateSigningKey, serializeInto, sSigningKeyName), fail); + require(errSecSuccess == appendPublicOctetsAndSize(fullID->publicSigningKey, serializeInto), fail); + return errSecSuccess; + +fail: + CFDataSetLength(serializeInto, start); + + return errSecParam; +} + + +bool SecOTRFIAppendSerialization(SecOTRFullIdentityRef fullID, CFMutableDataRef serializeInto, CFErrorRef *error) +{ + OSStatus status = SecOTRFIAppendV2Serialization(fullID, serializeInto); + if (errSecSuccess == status) { + return true; + } else { + SecOTRCreateError(secOTRErrorOSError, status, CFSTR("OSStatus returned in error code"), NULL, error); + return false; + } +} + +size_t SecOTRFISignatureSize(SecOTRFullIdentityRef fullID) +{ + return SecKeyGetSize(fullID->publicSigningKey, kSecKeySignatureSize); +} + +bool SecOTRFIAppendSignature(SecOTRFullIdentityRef fullID, + CFDataRef dataToHash, + CFMutableDataRef appendTo, + CFErrorRef *error) +{ + const size_t signatureSize = SecOTRFISignatureSize(fullID); + const CFIndex sourceLength = CFDataGetLength(dataToHash); + const uint8_t* sourceData = CFDataGetBytePtr(dataToHash); + + CFIndex start = CFDataGetLength(appendTo); + + require(((CFIndex)signatureSize) >= 0, fail); + + CFDataIncreaseLength(appendTo, (CFIndex)signatureSize + 1); + + uint8_t *size = CFDataGetMutableBytePtr(appendTo) + start; + uint8_t* signatureStart = size + 1; + size_t signatureUsed = signatureSize; + + require(SecKeyDigestAndSignWithError(fullID->privateSigningKey, kOTRSignatureAlgIDPtr, + sourceData, (size_t)sourceLength, + signatureStart, &signatureUsed, error), fail); + + require(signatureUsed < 256, fail); + require(((CFIndex)signatureUsed) >= 0, fail); + *size = signatureUsed; + + CFDataSetLength(appendTo, start + (CFIndex)signatureUsed + 1); + + return true; + +fail: + CFDataSetLength(appendTo, start); + + return false; +} + +void SecOTRFIAppendPublicHash(SecOTRFullIdentityRef fullID, CFMutableDataRef appendTo) +{ + CFDataAppendBytes(appendTo, fullID->publicIDHash, sizeof(fullID->publicIDHash)); +} + +bool SecOTRFIComparePublicHash(SecOTRFullIdentityRef fullID, const uint8_t hash[kMPIDHashSize]) +{ + return 0 == memcmp(hash, fullID->publicIDHash, kMPIDHashSize); +}