#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";
}
}
+CFStringRef SOSCoderGetID(SOSCoderRef coder) {
+ return coder->peer_id;
+}
+
/*
static void logRawCoderMessage(const uint8_t* der, uint8_t* der_end, bool encoding)
{
}
*/
-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;
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))
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);
}
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;
}
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;
}
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;
CFReleaseNull(privateKey);
- publicKey = SOSPeerInfoCopyPubKey(peerInfo);
+ publicKey = SOSPeerInfoCopyPubKey(peerInfo, &localError);
+ require(publicKey, errOut);
peerRef = SecOTRPublicIdentityCreateFromSecKeyRef(allocator, publicKey, &localError);
require_quiet(peerRef, errOut);
coder->waitingForDataPacket = false;
coder->pendingResponse = NULL;
-
+
CFReleaseNull(publicKey);
CFReleaseNull(privateKey);
CFReleaseNull(myRef);
secnotice("coder", "NULL Coder requested, no transport security");
}
+ coder->hashOfLastReceived = CFDataCreateMutableWithScratch(kCFAllocatorDefault, lastReceived_di()->output_size);
+ coder->lastReceivedWasOld = false;
+
SOSCoderStart(coder, NULL);
return coder;
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)
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)
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
}
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) {
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: