#include <stdlib.h>
#include <CoreFoundation/CFBase.h>
#include <CoreFoundation/CFError.h>
#include <Security/SecBasePriv.h>
#include <Security/SecOTR.h>
#include <Security/SecOTRSession.h>
#include <Security/SecOTRSessionPriv.h>
#include "keychain/SecureObjectSync/SOSInternal.h"
#include "keychain/SecureObjectSync/SOSFullPeerInfo.h"
#include <Security/SecureObjectSync/SOSPeerInfo.h>
#include "keychain/SecureObjectSync/SOSPeer.h"
#include "keychain/SecureObjectSync/SOSCoder.h"
#include <utilities/SecCFRelease.h>
#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 <utilities/der_plist_internal.h>
#include <corecrypto/ccder.h>
#include <utilities/iCloudKeychainTrace.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){
__block CFStringRef desc = NULL;
CFDataPerformWithHexString(coder->hashOfLastReceived, ^(CFStringRef dataString) {
desc = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("<Coder %@ %@ %s%s>"),
coder->sessRef,
dataString,
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;
return CFStringCompare(coderA->peer_id, coderB->peer_id, 0) == kCFCompareEqualTo;
}
static const char *SOSCoderString(SOSCoderStatus coderStatus) {
switch (coderStatus) {
case kSOSCoderDataReturned: return "DataReturned";
case kSOSCoderNegotiating: return "Negotiating";
case kSOSCoderNegotiationCompleted: return "NegotiationCompleted";
case kSOSCoderFailure: return "Failure";
case kSOSCoderStaleEvent: return "StaleEvent";
case kSOSCoderTooNew: return "TooNew";
default: return "StatusUnknown";
}
}
static CFMutableDataRef sessSerializedCreate(SOSCoderRef coder, CFErrorRef *error) {
CFMutableDataRef otr_state = NULL;
if(!coder || !coder->sessRef) {
SOSCreateErrorWithFormat(kSOSErrorUnexpectedType, NULL, error, 0, CFSTR("No session reference."));
return NULL;
}
if ((otr_state = CFDataCreateMutable(NULL, 0)) == NULL) {
SOSCreateErrorWithFormat(kSOSErrorAllocationFailure, NULL, error, 0, CFSTR("Mutable Data allocation failed."));
return NULL;
}
if (errSecSuccess != SecOTRSAppendSerialization(coder->sessRef, otr_state)) {
SOSCreateErrorWithFormat(kSOSErrorEncodeFailure, NULL, error, 0, CFSTR("Append Serialization failed."));
CFReleaseSafe(otr_state);
return NULL;
}
return otr_state;
}
static size_t der_sizeof_optional_data(CFDataRef data) {
return data ? der_sizeof_data(data, NULL) : 0;
}
static uint8_t* der_encode_optional_data(CFDataRef data, CFErrorRef *error, const uint8_t* der, uint8_t* der_end) {
return data ? der_encode_data(data, error, der, der_end) : der_end;
}
static size_t SOSCoderGetDEREncodedSize(SOSCoderRef coder, CFErrorRef *error) {
size_t encoded_size = 0;
CFMutableDataRef otr_state = sessSerializedCreate(coder, error);
if (otr_state) {
size_t data_size = der_sizeof_data(otr_state, error);
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))
{
encoded_size = ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, data_size + waiting_size + pending_size);
}
CFReleaseSafe(otr_state);
}
return encoded_size;
}
static uint8_t* SOSCoderEncodeToDER(SOSCoderRef coder, CFErrorRef* error, const uint8_t* der, uint8_t* der_end) {
if(!der_end) return NULL;
uint8_t* result = NULL;
CFMutableDataRef otr_state = sessSerializedCreate(coder, error);
if(otr_state) {
result = ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
der_encode_data(otr_state, error, der,
ccder_encode_bool(coder->waitingForDataPacket, der,
der_encode_optional_data(coder->pendingResponse, error, der, der_end))));
CFReleaseSafe(otr_state);
}
return result;
}
bool SOSCoderIsCoderInAwaitingState(SOSCoderRef coder){
return SecOTRSessionIsSessionInAwaitingState(coder->sessRef);
}
CFDataRef SOSCoderCopyDER(SOSCoderRef coder, CFErrorRef* error) {
CFMutableDataRef encoded = NULL;
size_t encoded_size = SOSCoderGetDEREncodedSize(coder, error);
if (encoded_size > 0) {
encoded = CFDataCreateMutable(NULL, encoded_size);
if (encoded) {
CFDataSetLength(encoded, encoded_size);
uint8_t * der = CFDataGetMutableBytePtr(encoded);
uint8_t * der_end = der + encoded_size;
if (!SOSCoderEncodeToDER(coder, error, der, der_end)) {
CFReleaseNull(encoded);
encoded = NULL;
}
}
}
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;
}
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: 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) {
SOSCoderRef p = SOSCoderCreate_internal();
const uint8_t *der = CFDataGetBytePtr(exportedData);
const uint8_t *der_end = der + CFDataGetLength(exportedData);
CFDataRef otr_data = NULL;
switch (SOSCoderGetExportedVersion(der, der_end)) {
case kCoderAsOTRDataOnly:
der = der_decode_data(kCFAllocatorDefault, &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, &otr_data, error, der, sequence_end);
der = ccder_decode_bool(&p->waitingForDataPacket, der, sequence_end);
if (der != sequence_end) { der = der_decode_data(kCFAllocatorDefault, &p->pendingResponse, error, der, sequence_end);
}
}
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, &otr_data, error, 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, &p->hashOfLastReceived, error, der, sequence_end);
if (der != sequence_end) { der = der_decode_data(kCFAllocatorDefault, &p->pendingResponse, error, der, sequence_end);
}
}
break;
default:
SOSErrorCreate(kSOSErrorDecodeFailure, error, NULL, CFSTR("Unsupported SOS Coder DER"));
goto fail;
}
require(der, 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:
CFReleaseNull(p);
CFReleaseSafe(otr_data);
return NULL;
}
SOSCoderRef SOSCoderCreate(SOSPeerInfoRef peerInfo, SOSFullPeerInfoRef myPeerInfo, CFBooleanRef useCompact, CFErrorRef *error) {
CFAllocatorRef allocator = CFGetAllocator(peerInfo);
SOSCoderRef coder = SOSCoderCreate_internal();
CFErrorRef localError = NULL;
SecOTRFullIdentityRef myRef = NULL;
SecOTRPublicIdentityRef peerRef = NULL;
SecKeyRef privateKey = NULL;
SecKeyRef publicKey = NULL;
if (myPeerInfo && peerInfo) {
privateKey = SOSFullPeerInfoCopyDeviceKey(myPeerInfo, &localError);
require_quiet(privateKey, errOut);
myRef = SecOTRFullIdentityCreateFromSecKeyRefSOS(allocator, privateKey, &localError);
require_quiet(myRef, errOut);
CFReleaseNull(privateKey);
publicKey = SOSPeerInfoCopyPubKey(peerInfo, &localError);
require(publicKey, errOut);
peerRef = SecOTRPublicIdentityCreateFromSecKeyRef(allocator, publicKey, &localError);
require_quiet(peerRef, errOut);
if(useCompact == kCFBooleanTrue)
coder->sessRef = SecOTRSessionCreateFromIDAndFlags(allocator, myRef, peerRef, kSecOTRUseAppleCustomMessageFormat);
else
coder->sessRef = SecOTRSessionCreateFromID(allocator, myRef, peerRef);
require(coder->sessRef, errOut);
coder->waitingForDataPacket = false;
coder->pendingResponse = NULL;
CFReleaseNull(publicKey);
CFReleaseNull(privateKey);
CFReleaseNull(myRef);
CFReleaseNull(peerRef);
} else {
secnotice("coder", "NULL Coder requested, no transport security");
}
coder->hashOfLastReceived = CFDataCreateMutableWithScratch(kCFAllocatorDefault, lastReceived_di()->output_size);
coder->lastReceivedWasOld = false;
SOSCoderStart(coder, NULL);
return coder;
errOut:
secerror("Coder create failed: %@\n", localError ? localError : (CFTypeRef)CFSTR("No local error in SOSCoderCreate"));
secerror("Coder create failed: %@\n", error ? *error : (CFTypeRef)CFSTR("WTF NULL?"));
CFReleaseNull(myRef);
CFReleaseNull(peerRef);
CFReleaseNull(publicKey);
CFReleaseNull(privateKey);
CFReleaseNull(coder);
return NULL;
}
static void SOSCoderDestroy(CFTypeRef cf)
{
SOSCoderRef coder = (SOSCoderRef) cf;
if (coder) {
CFReleaseNull(coder->sessRef);
CFReleaseNull(coder->pendingResponse);
CFReleaseNull(coder->hashOfLastReceived);
}
}
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 coder->pendingResponse ? CFDataCreateCopy(kCFAllocatorDefault, coder->pendingResponse) : NULL;
}
void SOSCoderConsumeResponse(SOSCoderRef coder)
{
CFReleaseNull(coder->pendingResponse);
}
static bool SOSOTRSAppendStartPacket(SecOTRSessionRef session, CFMutableDataRef appendPacket, CFErrorRef *error) {
OSStatus otrStatus = SecOTRSAppendStartPacket(session, appendPacket);
if (otrStatus != errSecSuccess) {
SOSCreateErrorWithFormat(kSOSErrorEncodeFailure, (error != NULL) ? *error : NULL, error, NULL, CFSTR("append start packet returned: %" PRIdOSStatus), otrStatus);
}
return otrStatus == errSecSuccess;
}
SOSCoderStatus
SOSCoderStart(SOSCoderRef coder, CFErrorRef *error) {
CFMutableStringRef action = CFStringCreateMutable(kCFAllocatorDefault, 0);
CFStringRef beginState = NULL;
SOSCoderStatus result = kSOSCoderFailure;
CFMutableDataRef startPacket = NULL;
require_action_quiet(coder->sessRef, coderFailure, CFStringAppend(action, CFSTR("*** no otr session ***")));
beginState = CFCopyDescription(coder->sessRef);
require_action_quiet(!coder->waitingForDataPacket, negotiatingOut, CFStringAppend(action, CFSTR("waiting for peer to send first data packet")));
require_action_quiet(!SecOTRSGetIsReadyForMessages(coder->sessRef), coderFailure, CFStringAppend(action, CFSTR("otr session ready"));
result = kSOSCoderDataReturned);
require_action_quiet(SecOTRSGetIsIdle(coder->sessRef), negotiatingOut, CFStringAppend(action, CFSTR("otr negotiating already")));
require_action_quiet(startPacket = CFDataCreateMutable(kCFAllocatorDefault, 0), coderFailure, SOSCreateError(kSOSErrorAllocationFailure, CFSTR("alloc failed"), NULL, error));
require_quiet(SOSOTRSAppendStartPacket(coder->sessRef, startPacket, error), coderFailure);
CFRetainAssign(coder->pendingResponse, startPacket);
negotiatingOut:
result = kSOSCoderNegotiating;
coderFailure:
if (result == kSOSCoderFailure && error && *error)
CFStringAppendFormat(action, NULL, CFSTR(" %@"), *error);
secinfo("coder", "%@ %s %@ %@ returned %s", beginState,
SecOTRPacketTypeString(startPacket), action, coder->sessRef, SOSCoderString(result));
CFReleaseNull(startPacket);
CFReleaseSafe(beginState);
CFRelease(action);
return result;
}
SOSCoderStatus
SOSCoderResendDH(SOSCoderRef coder, CFErrorRef *error) {
if(coder->sessRef == NULL) return kSOSCoderDataReturned;
CFMutableDataRef startPacket = CFDataCreateMutable(kCFAllocatorDefault, 0);
SOSCoderStatus result = kSOSCoderFailure;
require_noerr_quiet(SecOTRSAppendRestartPacket(coder->sessRef, startPacket), exit);
secnotice("coder", "Resending OTR Start %@", startPacket);
CFRetainAssign(coder->pendingResponse, startPacket);
result = kSOSCoderNegotiating;
exit:
CFReleaseNull(startPacket);
return result;
}
static SOSCoderStatus nullCoder(CFDataRef from, CFMutableDataRef *to) {
*to = CFDataCreateMutableCopy(NULL, CFDataGetLength(from), from);
return kSOSCoderDataReturned;
}
SOSCoderStatus SOSCoderUnwrap(SOSCoderRef coder, CFDataRef codedMessage, CFMutableDataRef *message,
CFStringRef clientId, CFErrorRef *error) {
if(codedMessage == NULL) return kSOSCoderDataReturned;
if(coder->sessRef == NULL) return nullCoder(codedMessage, message);
CFMutableStringRef action = CFStringCreateMutable(kCFAllocatorDefault, 0);
SOSCoderStatus result = kSOSCoderFailure;
CFStringRef beginState = CFCopyDescription(coder->sessRef);
enum SecOTRSMessageKind kind = SecOTRSGetMessageKind(coder->sessRef, codedMessage);
switch (kind) {
case kOTRNegotiationPacket: {
CFMutableDataRef response = CFDataCreateMutable(kCFAllocatorDefault, 0);
OSStatus ppstatus = errSecSuccess;
if (response) {
switch (ppstatus = SecOTRSProcessPacket(coder->sessRef, codedMessage, response)) {
case errSecSuccess:
if (CFDataGetLength(response) > 1) {
CFStringAppendFormat(action, NULL, CFSTR("Sending OTR Response %s"), SecOTRPacketTypeString(response));
CFRetainAssign(coder->pendingResponse, response);
result = kSOSCoderNegotiating;
if (SecOTRSGetIsReadyForMessages(coder->sessRef)) {
CFStringAppend(action, CFSTR(" begin waiting for data packet"));
coder->waitingForDataPacket = true;
}
} else if(!SecOTRSGetIsReadyForMessages(coder->sessRef)) {
CFStringAppend(action, CFSTR("stuck?"));
result = kSOSCoderNegotiating;
} else {
CFStringAppend(action, CFSTR("completed negotiation"));
result = kSOSCoderNegotiationCompleted;
coder->waitingForDataPacket = false;
}
break;
case errSecDecode:
CFStringAppend(action, CFSTR("resending dh"));
result = SOSCoderResendDH(coder, error);
break;
default:
SOSCreateErrorWithFormat(kSOSErrorEncodeFailure, (error != NULL) ? *error : NULL, error, NULL, CFSTR("%@ Cannot negotiate session (%ld)"), clientId, (long)ppstatus);
result = kSOSCoderFailure;
break;
};
} else {
SOSCreateErrorWithFormat(kSOSErrorAllocationFailure, (error != NULL) ? *error : NULL, error, NULL, CFSTR("%@ Cannot allocate CFData"), clientId);
result = kSOSCoderFailure;
}
CFReleaseNull(response);
break;
}
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 for data; resending DH packet"));
result = SOSCoderResendDH(coder, error);
} else {
if (coder->waitingForDataPacket) {
CFStringAppend(action, CFSTR("got data packet we were waiting for "));
coder->waitingForDataPacket = false;
}
CFMutableDataRef exposed = CFDataCreateMutable(0, 0);
OSStatus otrResult = SecOTRSVerifyAndExposeMessage(coder->sessRef, codedMessage, exposed);
CFStringAppend(action, CFSTR("verify and expose message"));
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"));
result = kSOSCoderFailure;
} else {
CFStringAppend(action, CFSTR(" too old, forcing message"));
result = kSOSCoderForceMessage;
}
}
break;
case errSecOTRIDTooNew:
CFStringAppend(action, CFSTR(" too new"));
result = kSOSCoderTooNew;
break;
default:
SecError(otrResult, error, CFSTR("%@ Cannot expose message: %" PRIdOSStatus), clientId, otrResult);
secerror("%@ Decode OTR Protected Packet: %@", clientId, error ? *error : NULL);
result = kSOSCoderFailure;
break;
}
CFReleaseNull(exposed);
}
CFReleaseNull(previousMessageHash);
}
break;
default:
secerror("%@ Unknown packet type: %@", clientId, codedMessage);
SOSCreateError(kSOSErrorDecodeFailure, CFSTR("Unknown packet type"), (error != NULL) ? *error : NULL, error);
result = kSOSCoderFailure;
break;
};
if (result == kSOSCoderFailure && error && *error)
CFStringAppendFormat(action, NULL, CFSTR(" %@"), *error);
secnotice("coder", "%@ %@ %s %@ %@ returned %s", clientId, beginState,
SecOTRPacketTypeString(codedMessage), action, coder->sessRef, SOSCoderString(result));
CFReleaseSafe(beginState);
CFRelease(action);
return result;
}
SOSCoderStatus SOSCoderWrap(SOSCoderRef coder, CFDataRef message, CFMutableDataRef *codedMessage, CFStringRef clientId, CFErrorRef *error) {
CFMutableStringRef action = CFStringCreateMutable(kCFAllocatorDefault, 0);
SOSCoderStatus result = kSOSCoderDataReturned;
CFStringRef beginState = NULL;
CFMutableDataRef encoded = NULL;
OSStatus otrStatus = 0;
require_action_quiet(coder->sessRef, errOut,
CFStringAppend(action, CFSTR("*** using null coder ***"));
result = nullCoder(message, codedMessage));
beginState = CFCopyDescription(coder->sessRef);
require_action_quiet(SecOTRSGetIsReadyForMessages(coder->sessRef), errOut,
CFStringAppend(action, CFSTR("not ready"));
result = kSOSCoderNegotiating);
require_action_quiet(!coder->waitingForDataPacket, errOut,
CFStringAppend(action, CFSTR("waiting for peer to send data packet first"));
result = kSOSCoderNegotiating);
require_action_quiet(encoded = CFDataCreateMutable(kCFAllocatorDefault, 0), errOut,
SOSCreateErrorWithFormat(kSOSErrorAllocationFailure, NULL, error, NULL, CFSTR("%@ alloc failed"), clientId);
result = kSOSCoderFailure);
require_noerr_action_quiet(otrStatus = SecOTRSSignAndProtectMessage(coder->sessRef, message, encoded), errOut,
SOSCreateErrorWithFormat(kSOSErrorEncodeFailure, (error != NULL) ? *error : NULL, error, NULL, CFSTR("%@ cannot protect message: %" PRIdOSStatus), clientId, otrStatus);
CFReleaseNull(encoded);
result = kSOSCoderFailure);
*codedMessage = encoded;
errOut:
if (result == kSOSCoderFailure && error && *error)
CFStringAppendFormat(action, NULL, CFSTR(" %@"), *error);
secinfo("coder", "%@ %@ %s %@ %@ returned %s", clientId, beginState,
SecOTRPacketTypeString(encoded), action, coder->sessRef, SOSCoderString(result));
CFReleaseSafe(beginState);
CFRelease(action);
return result;
}
bool SOSCoderCanWrap(SOSCoderRef coder) {
return coder->sessRef && SecOTRSGetIsReadyForMessages(coder->sessRef) && !coder->waitingForDataPacket;
}