]> git.saurik.com Git - apple/security.git/blobdiff - OSX/sec/SOSCircle/SecureObjectSync/SOSCoder.c
Security-57740.20.22.tar.gz
[apple/security.git] / OSX / sec / SOSCircle / SecureObjectSync / SOSCoder.c
index 1e6317bffb29f1afe27c4f0b1ea9ebd5b7df1e1f..5e6e9ae37b3e18c254b606d2e016fe20671228f2 100644 (file)
@@ -39,6 +39,7 @@
 #include <utilities/SecCFWrappers.h>
 #include <utilities/SecIOFormat.h>
 #include <utilities/SecCFError.h>
+#include <utilities/SecCoreCrypto.h>
 #include <utilities/debugging.h>
 
 #include <utilities/der_plist.h>
 #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 %@ %@ %@ %s%s>"),
+                                                    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: