X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/5c19dc3ae3bd8e40a9c028b0deddd50ff337692c..3a7be6fd655a5b07ffb821947ed6ba5fdc4bea8c:/OSX/sec/SOSCircle/SecureObjectSync/SOSCoder.c diff --git a/OSX/sec/SOSCircle/SecureObjectSync/SOSCoder.c b/OSX/sec/SOSCircle/SecureObjectSync/SOSCoder.c index 1e6317bf..5e6e9ae3 100644 --- a/OSX/sec/SOSCircle/SecureObjectSync/SOSCoder.c +++ b/OSX/sec/SOSCircle/SecureObjectSync/SOSCoder.c @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -50,12 +51,44 @@ #include "AssertMacros.h" struct __OpaqueSOSCoder { + CFRuntimeBase _base; + CFStringRef peer_id; SecOTRSessionRef sessRef; bool waitingForDataPacket; CFDataRef pendingResponse; + + CFDataRef hashOfLastReceived; + bool lastReceivedWasOld; }; +#define lastReceived_di ccsha1_di + +CFGiblisWithCompareFor(SOSCoder) + +static CFStringRef SOSCoderCopyFormatDescription(CFTypeRef cf, CFDictionaryRef formatOptions) { + SOSCoderRef coder = (SOSCoderRef)cf; + if(coder){ + CFStringRef desc = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR(""), + coder->peer_id, + coder->sessRef, + coder->hashOfLastReceived, + coder->waitingForDataPacket ? "W" : "w", + coder->lastReceivedWasOld ? "O" : "o" + ); + return desc; + } + else + return CFSTR("NULL"); +} + +static Boolean SOSCoderCompare(CFTypeRef cfA, CFTypeRef cfB) { + SOSCoderRef coderA = (SOSCoderRef)cfA, coderB = (SOSCoderRef)cfB; + // Use mainly to see if peerB is actually this device (peerA) + return CFStringCompare(coderA->peer_id, coderB->peer_id, 0) == kCFCompareEqualTo; +} + + static const char *SOSCoderString(SOSCoderStatus coderStatus) { switch (coderStatus) { case kSOSCoderDataReturned: return "DataReturned"; @@ -68,6 +101,10 @@ static const char *SOSCoderString(SOSCoderStatus coderStatus) { } } +CFStringRef SOSCoderGetID(SOSCoderRef coder) { + return coder->peer_id; +} + /* static void logRawCoderMessage(const uint8_t* der, uint8_t* der_end, bool encoding) { @@ -85,33 +122,6 @@ static const char *SOSCoderString(SOSCoderStatus coderStatus) { } */ -static size_t der_sizeof_bool(bool value) { - return ccder_sizeof(CCDER_BOOLEAN, 1); -} - -static uint8_t* der_encode_bool(bool value, const uint8_t *der, uint8_t *der_end) { - uint8_t valueByte = value; - return ccder_encode_tl(CCDER_BOOLEAN, 1, der, - ccder_encode_body(1, &valueByte, der, der_end)); -} - -static const uint8_t* der_decode_bool(bool *value, const uint8_t *der, const uint8_t *der_end) { - size_t payload_size = 0; - - der = ccder_decode_tl(CCDER_BOOLEAN, &payload_size, der, der_end); - - if (payload_size != 1) { - der = NULL; - } - - if (der != NULL) { - *value = (*der != 0); - der++; - } - - return der; -} - static CFMutableDataRef sessSerialized(SOSCoderRef coder, CFErrorRef *error) { CFMutableDataRef otr_state = NULL; @@ -151,7 +161,7 @@ static size_t SOSCoderGetDEREncodedSize(SOSCoderRef coder, CFErrorRef *error) { if (otr_state) { size_t data_size = der_sizeof_data(otr_state, error); - size_t waiting_size = der_sizeof_bool(coder->waitingForDataPacket); + size_t waiting_size = ccder_sizeof_bool(coder->waitingForDataPacket, error); size_t pending_size = der_sizeof_optional_data(coder->pendingResponse); if ((data_size != 0) && (waiting_size != 0)) @@ -172,7 +182,7 @@ static uint8_t* SOSCoderEncodeToDER(SOSCoderRef coder, CFErrorRef* error, const if(otr_state) { result = ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der, der_encode_data(otr_state, error, der, - der_encode_bool(coder->waitingForDataPacket, der, + ccder_encode_bool(coder->waitingForDataPacket, der, der_encode_optional_data(coder->pendingResponse, error, der, der_end)))); CFReleaseSafe(otr_state); } @@ -199,45 +209,129 @@ CFDataRef SOSCoderCopyDER(SOSCoderRef coder, CFErrorRef* error) { return encoded; } +static SOSCoderRef SOSCoderCreate_internal() { + SOSCoderRef p = CFTypeAllocate(SOSCoder, struct __OpaqueSOSCoder, kCFAllocatorDefault); + + p->peer_id = NULL; + p->sessRef = NULL; + p->pendingResponse = NULL; + p->waitingForDataPacket = false; + + p->hashOfLastReceived = NULL; + p->lastReceivedWasOld = false; + + return p; + +} + +// 0 - Type not understood +// 1 - OCTET_STRING, just stored the data for OTR +// 2 - SEQUENCE with no version value +// 3 - SEQUENCE with version value we pull out of the CCDER_INTEGER + +typedef enum coderExportFormatVersion { + kNotUnderstood = 0, + kCoderAsOTRDataOnly = 1, + kCoderAsSequence = 2, + kCoderAsVersionedSequence = 3, + + kCurrentCoderExportVersion = kCoderAsVersionedSequence +} CoderExportFormatVersion; + +static uint64_t SOSCoderGetExportedVersion(const uint8_t *der, const uint8_t *der_end) { + ccder_tag tag; + uint64_t result = kNotUnderstood; + require(ccder_decode_tag(&tag, der, der_end),xit); + switch (tag) { + case CCDER_OCTET_STRING: // TODO: this code is safe to delete? + result = kCoderAsOTRDataOnly; + break; + + case CCDER_CONSTRUCTED_SEQUENCE: + { + const uint8_t *sequence_end = NULL; + der = ccder_decode_sequence_tl(&sequence_end, der, der_end); + ccder_tag firstSequenceTag; + require(ccder_decode_tag(&firstSequenceTag, der, der_end),xit); + + switch (firstSequenceTag) { + case CCDER_OCTET_STRING: + result = kCoderAsSequence; + break; + case CCDER_INTEGER: + der = ccder_decode_uint64(NULL, der, sequence_end); + if (der == NULL) { + result = kNotUnderstood; + } else { + result = kCoderAsVersionedSequence; + } + break; + } + } + } +xit: + return result; + +} + SOSCoderRef SOSCoderCreateFromData(CFDataRef exportedData, CFErrorRef *error) { // TODO: fill in errors for all failure cases //require_action_quiet(coder, xit, SOSCreateError(kSOSErrorSendFailure, CFSTR("No coder for peer"), NULL, error)); - SOSCoderRef p = calloc(1, sizeof(struct __OpaqueSOSCoder)); + SOSCoderRef p = SOSCoderCreate_internal(); const uint8_t *der = CFDataGetBytePtr(exportedData); const uint8_t *der_end = der + CFDataGetLength(exportedData); CFDataRef otr_data = NULL; - ccder_tag tag; - require(ccder_decode_tag(&tag, der, der_end),fail); - - switch (tag) { - case CCDER_OCTET_STRING: // TODO: this code is safe to delete? - { + switch (SOSCoderGetExportedVersion(der, der_end)) { + case kCoderAsOTRDataOnly: der = der_decode_data(kCFAllocatorDefault, 0, &otr_data, error, der, der_end); p->waitingForDataPacket = false; + break; + + case kCoderAsSequence: + { + const uint8_t *sequence_end = NULL; + der = ccder_decode_sequence_tl(&sequence_end, der, der_end); + + require_action_quiet(sequence_end == der_end, fail, SecCFDERCreateError(kSOSErrorDecodeFailure, CFSTR("Extra data in SOS coder"), NULL, error)); + + der = der_decode_data(kCFAllocatorDefault, 0, &otr_data, error, der, sequence_end); + der = ccder_decode_bool(&p->waitingForDataPacket, der, sequence_end); + if (der != sequence_end) { // optionally a pending response + der = der_decode_data(kCFAllocatorDefault, 0, &p->pendingResponse, error, der, sequence_end); + } } - break; - - case CCDER_CONSTRUCTED_SEQUENCE: + break; + + case kCoderAsVersionedSequence: { const uint8_t *sequence_end = NULL; der = ccder_decode_sequence_tl(&sequence_end, der, der_end); - + require_action_quiet(sequence_end == der_end, fail, SecCFDERCreateError(kSOSErrorDecodeFailure, CFSTR("Extra data in SOS coder"), NULL, error)); - + + uint64_t version; + der = ccder_decode_uint64(&version, der, sequence_end); + if (version != kCoderAsVersionedSequence) { + SOSErrorCreate(kSOSErrorDecodeFailure, error, NULL, CFSTR("Unsupported Sequence Version: %lld"), version); + goto fail; + } + der = der_decode_data(kCFAllocatorDefault, 0, &otr_data, error, der, sequence_end); - der = der_decode_bool(&p->waitingForDataPacket, der, sequence_end); + der = ccder_decode_bool(&p->waitingForDataPacket, der, sequence_end); + der = ccder_decode_bool(&p->lastReceivedWasOld, der, sequence_end); + der = der_decode_data(kCFAllocatorDefault, 0, &p->hashOfLastReceived, error, der, sequence_end); if (der != sequence_end) { // optionally a pending response der = der_decode_data(kCFAllocatorDefault, 0, &p->pendingResponse, error, der, sequence_end); } } - break; - + break; + default: - SecCFDERCreateError(kSOSErrorDecodeFailure, CFSTR("Unsupported SOS Coder DER"), NULL, error); + SOSErrorCreate(kSOSErrorDecodeFailure, error, NULL, CFSTR("Unsupported SOS Coder DER")); goto fail; } @@ -246,11 +340,14 @@ SOSCoderRef SOSCoderCreateFromData(CFDataRef exportedData, CFErrorRef *error) { p->sessRef = SecOTRSessionCreateFromData(NULL, otr_data); require(p->sessRef, fail); + if (p->hashOfLastReceived == NULL) + p->hashOfLastReceived = CFDataCreateMutableWithScratch(kCFAllocatorDefault, lastReceived_di()->output_size); + CFReleaseSafe(otr_data); return p; fail: - SOSCoderDispose(p); + CFReleaseNull(p); CFReleaseSafe(otr_data); return NULL; } @@ -259,7 +356,8 @@ fail: SOSCoderRef SOSCoderCreate(SOSPeerInfoRef peerInfo, SOSFullPeerInfoRef myPeerInfo, CFBooleanRef useCompact, CFErrorRef *error) { CFAllocatorRef allocator = CFGetAllocator(peerInfo); - SOSCoderRef coder = calloc(1, sizeof(struct __OpaqueSOSCoder)); + SOSCoderRef coder = SOSCoderCreate_internal(); + CFErrorRef localError = NULL; SecOTRFullIdentityRef myRef = NULL; @@ -276,7 +374,8 @@ SOSCoderRef SOSCoderCreate(SOSPeerInfoRef peerInfo, SOSFullPeerInfoRef myPeerInf CFReleaseNull(privateKey); - publicKey = SOSPeerInfoCopyPubKey(peerInfo); + publicKey = SOSPeerInfoCopyPubKey(peerInfo, &localError); + require(publicKey, errOut); peerRef = SecOTRPublicIdentityCreateFromSecKeyRef(allocator, publicKey, &localError); require_quiet(peerRef, errOut); @@ -291,7 +390,7 @@ SOSCoderRef SOSCoderCreate(SOSPeerInfoRef peerInfo, SOSFullPeerInfoRef myPeerInf coder->waitingForDataPacket = false; coder->pendingResponse = NULL; - + CFReleaseNull(publicKey); CFReleaseNull(privateKey); CFReleaseNull(myRef); @@ -300,6 +399,9 @@ SOSCoderRef SOSCoderCreate(SOSPeerInfoRef peerInfo, SOSFullPeerInfoRef myPeerInf secnotice("coder", "NULL Coder requested, no transport security"); } + coder->hashOfLastReceived = CFDataCreateMutableWithScratch(kCFAllocatorDefault, lastReceived_di()->output_size); + coder->lastReceivedWasOld = false; + SOSCoderStart(coder, NULL); return coder; @@ -312,19 +414,19 @@ errOut: CFReleaseNull(publicKey); CFReleaseNull(privateKey); - free(coder); + CFReleaseNull(coder); return NULL; } -void SOSCoderDispose(SOSCoderRef coder) +static void SOSCoderDestroy(CFTypeRef cf) { + SOSCoderRef coder = (SOSCoderRef) cf; if (coder) { CFReleaseNull(coder->sessRef); CFReleaseNull(coder->pendingResponse); CFReleaseNull(coder->peer_id); - free(coder); + CFReleaseNull(coder->hashOfLastReceived); } - coder = NULL; } void SOSCoderReset(SOSCoderRef coder) @@ -332,11 +434,40 @@ void SOSCoderReset(SOSCoderRef coder) SecOTRSessionReset(coder->sessRef); coder->waitingForDataPacket = false; CFReleaseNull(coder->pendingResponse); + + coder->lastReceivedWasOld = false; + CFReleaseNull(coder->hashOfLastReceived); + coder->hashOfLastReceived = CFDataCreateMutableWithScratch(kCFAllocatorDefault, lastReceived_di()->output_size); +} + +bool SOSCoderIsFor(SOSCoderRef coder, SOSPeerInfoRef peerInfo, SOSFullPeerInfoRef myPeerInfo) { + SecKeyRef theirPublicKey = NULL; + SecKeyRef myPublicKey = NULL; + bool isForThisPair = false; + CFErrorRef localError = NULL; + + myPublicKey = SOSPeerInfoCopyPubKey(SOSFullPeerInfoGetPeerInfo(myPeerInfo), &localError); + require(myPublicKey, errOut); + + theirPublicKey = SOSPeerInfoCopyPubKey(peerInfo, &localError); + require(theirPublicKey, errOut); + + isForThisPair = SecOTRSIsForKeys(coder->sessRef, myPublicKey, theirPublicKey); + +errOut: + if (localError) { + secerror("SOSCoderIsFor failed: %@\n", localError ? localError : (CFTypeRef)CFSTR("No local error in SOSCoderCreate")); + } + + CFReleaseNull(myPublicKey); + CFReleaseNull(theirPublicKey); + CFReleaseNull(localError); + return isForThisPair; } CFDataRef SOSCoderCopyPendingResponse(SOSCoderRef coder) { - return CFRetainSafe(coder->pendingResponse); + return coder->pendingResponse ? CFDataCreateCopy(kCFAllocatorDefault, coder->pendingResponse) : NULL; } void SOSCoderConsumeResponse(SOSCoderRef coder) @@ -417,6 +548,7 @@ SOSCoderStatus SOSCoderUnwrap(SOSCoderRef coder, CFDataRef codedMessage, CFMutab CFStringRef beginState = CFCopyDescription(coder->sessRef); enum SecOTRSMessageKind kind = SecOTRSGetMessageKind(coder->sessRef, codedMessage); + switch (kind) { case kOTRNegotiationPacket: { /* If we're in here we haven't completed negotiating a session. Use SecOTRSProcessPacket() to go through @@ -465,10 +597,15 @@ SOSCoderStatus SOSCoderUnwrap(SOSCoderRef coder, CFDataRef codedMessage, CFMutab } case kOTRDataPacket: + { + CFDataRef previousMessageHash = coder->hashOfLastReceived; + coder->hashOfLastReceived = CFDataCreateWithHash(kCFAllocatorDefault, lastReceived_di(), CFDataGetBytePtr(codedMessage), CFDataGetLength(codedMessage)); + bool lastWasOld = coder->lastReceivedWasOld; + coder->lastReceivedWasOld = false; + if(!SecOTRSGetIsReadyForMessages(coder->sessRef)) { - CFStringAppend(action, CFSTR("not ready, resending DH packet")); + CFStringAppend(action, CFSTR("not ready for data; resending DH packet")); SetCloudKeychainTraceValueForKey(kCloudKeychainNumberOfTimesSyncFailed, 1); - CFStringAppend(action, CFSTR("not ready for data; resending dh")); result = SOSCoderResendDH(coder, error); } else { if (coder->waitingForDataPacket) { @@ -478,27 +615,44 @@ SOSCoderStatus SOSCoderUnwrap(SOSCoderRef coder, CFDataRef codedMessage, CFMutab CFMutableDataRef exposed = CFDataCreateMutable(0, 0); OSStatus otrResult = SecOTRSVerifyAndExposeMessage(coder->sessRef, codedMessage, exposed); CFStringAppend(action, CFSTR("verify and expose message")); - if (otrResult) { - if (otrResult == errSecOTRTooOld) { - CFStringAppend(action, CFSTR(" too old")); - result = kSOSCoderStaleEvent; - } - else if(otrResult == errSecOTRIDTooNew){ + switch(otrResult) { + case errSecSuccess: + CFStringAppend(action, CFSTR("decoded OTR protected packet")); + CFTransferRetained(*message, exposed); + result = kSOSCoderDataReturned; + break; + case errSecOTRTooOld: + if (CFEqualSafe(previousMessageHash, coder->hashOfLastReceived)) { + CFStringAppend(action, CFSTR(" repeated")); + result = kSOSCoderStaleEvent; + } else { + coder->lastReceivedWasOld = true; + if (lastWasOld) { + CFStringAppend(action, CFSTR(" too old, repeated renegotiating")); + // Fail so we will renegotiate + result = kSOSCoderFailure; + } else { + CFStringAppend(action, CFSTR(" too old, forcing message")); + // Force message send. + result = kSOSCoderForceMessage; + } + } + break; + case errSecOTRIDTooNew: CFStringAppend(action, CFSTR(" too new")); result = kSOSCoderTooNew; - }else { + break; + default: SecError(otrResult, error, CFSTR("%@ Cannot expose message: %" PRIdOSStatus), clientId, otrResult); secerror("%@ Decode OTR Protected Packet: %@", clientId, error ? *error : NULL); result = kSOSCoderFailure; - } - } else { - CFStringAppend(action, CFSTR("decoded OTR protected packet")); - *message = exposed; - exposed = NULL; - result = kSOSCoderDataReturned; + break; } + CFReleaseNull(exposed); } + CFReleaseNull(previousMessageHash); + } break; default: