1//
2//  SOSCoder.c
3//  sec
4//
5//  Created by Richard Murphy on 2/6/13.
6//
7//
8#include <stdlib.h>
9
10#include <CoreFoundation/CFBase.h>
11#include <CoreFoundation/CFError.h>
12
13#include <Security/SecBasePriv.h>
14#include <Security/SecOTR.h>
15#include <Security/SecOTRSession.h>
16#include <SecureObjectSync/SOSInternal.h>
17#include <SecureObjectSync/SOSFullPeerInfo.h>
18#include <SecureObjectSync/SOSPeerInfo.h>
19#include <SecureObjectSync/SOSPeer.h>
20#include <SecureObjectSync/SOSCoder.h>
21
22#include <utilities/SecCFRelease.h>
23#include <utilities/SecCFWrappers.h>
24#include <utilities/SecIOFormat.h>
25#include <utilities/SecCFError.h>
26#include <utilities/debugging.h>
27
28#include <utilities/der_plist.h>
29#include <utilities/der_plist_internal.h>
30
31#include <corecrypto/ccder.h>
32#include <utilities/iCloudKeychainTrace.h>
33
34#include "AssertMacros.h"
35
36struct __OpaqueSOSCoder {
37    CFStringRef peer_id;
38    SecOTRSessionRef sessRef;
39    bool waitingForDataPacket;
40};
41
42static const char *SOSPeerCoderString(SOSPeerCoderStatus coderStatus) {
43    switch (coderStatus) {
44        case kSOSPeerCoderDataReturned: return "DataReturned";
45        case kSOSPeerCoderNegotiating: return "Negotiating";
46        case kSOSPeerCoderNegotiationCompleted: return "NegotiationCompleted";
47        case kSOSPeerCoderFailure: return "Failure";
48        case kSOSPeerCoderStaleEvent: return "StaleEvent";
49        default: return "StatusUnknown";
50    }
51}
52
53static size_t der_sizeof_bool(bool value) {
54    return ccder_sizeof(CCDER_BOOLEAN, 1);
55}
56
57static uint8_t* der_encode_bool(bool value, const uint8_t *der, uint8_t *der_end) {
58    uint8_t valueByte = value;
59    return ccder_encode_tl(CCDER_BOOLEAN, 1, der,
60              ccder_encode_body(1, &valueByte, der, der_end));
61}
62
63static const uint8_t* der_decode_bool(bool *value, const uint8_t *der, const uint8_t *der_end) {
64    size_t payload_size = 0;
65
66    der = ccder_decode_tl(CCDER_BOOLEAN, &payload_size, der, der_end);
67
68    if (payload_size != 1) {
69        der = NULL;
70    }
71
72    if (der != NULL) {
73        *value = (*der != 0);
74        der++;
75    }
76
77    return der;
78}
79
80static CFMutableDataRef sessSerialized(SOSCoderRef coder, CFErrorRef *error) {
81    CFMutableDataRef otr_state = NULL;
82
83    if(!coder || !coder->sessRef) {
84        SOSCreateErrorWithFormat(kSOSErrorUnexpectedType, NULL, error, 0, CFSTR("No session reference."));
85        return NULL;
86    }
87
88    if ((otr_state = CFDataCreateMutable(NULL, 0)) == NULL) {
89        SOSCreateErrorWithFormat(kSOSErrorAllocationFailure, NULL, error, 0, CFSTR("Mutable Data allocation failed."));
90        return NULL;
91    }
92
93    if (errSecSuccess != SecOTRSAppendSerialization(coder->sessRef, otr_state)) {
94        SOSCreateErrorWithFormat(kSOSErrorEncodeFailure, NULL, error, 0, CFSTR("Append Serialization failed."));
95        CFReleaseSafe(otr_state);
96        return NULL;
97    }
98
99    return otr_state;
100
101}
102
103static size_t SOSCoderGetDEREncodedSize(SOSCoderRef coder, CFErrorRef *error) {
104    size_t encoded_size = 0;
105    CFMutableDataRef otr_state = sessSerialized(coder, error);
106
107    if (otr_state) {
108        size_t data_size = der_sizeof_data(otr_state, error);
109        size_t waiting_size = der_sizeof_bool(coder->waitingForDataPacket);
110
111        if ((data_size != 0) && (waiting_size != 0))
112        {
113            encoded_size = ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, data_size + waiting_size);
114        }
115        CFReleaseSafe(otr_state);
116    }
117    return encoded_size;
118}
119
120static uint8_t* SOSCoderEncodeToDER(SOSCoderRef coder, CFErrorRef* error, const uint8_t* der, uint8_t* der_end) {
121    if(!der_end) return NULL;
122    uint8_t* result = NULL;
123    CFMutableDataRef otr_state = sessSerialized(coder, error);
124
125    if(otr_state) {
126        result = ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
127                 der_encode_data(otr_state, error, der,
128                 der_encode_bool(coder->waitingForDataPacket, der, der_end)));
129        CFReleaseSafe(otr_state);
130    }
131    return result;
132}
133
134
135CFDataRef SOSCoderCopyDER(SOSCoderRef coder, CFErrorRef* error) {
136    CFMutableDataRef encoded = NULL;
137    size_t encoded_size = SOSCoderGetDEREncodedSize(coder, error);
138
139    if (encoded_size > 0) {
140        encoded = CFDataCreateMutable(NULL, encoded_size);
141        if (encoded) {
142            CFDataSetLength(encoded, encoded_size);
143            uint8_t * der = CFDataGetMutableBytePtr(encoded);
144            uint8_t * der_end = der + encoded_size;
145            if (!SOSCoderEncodeToDER(coder, error, der, der_end)) {
146                CFReleaseNull(encoded);
147                encoded = NULL;
148            }
149        }
150    }
151    return encoded;
152}
153
154SOSCoderRef SOSCoderCreateFromData(CFDataRef exportedData, CFErrorRef *error) {
155
156    SOSCoderRef p = calloc(1, sizeof(struct __OpaqueSOSCoder));
157
158    const uint8_t *der = CFDataGetBytePtr(exportedData);
159    const uint8_t *der_end = der + CFDataGetLength(exportedData);
160
161    CFDataRef otr_data = NULL;
162
163    ccder_tag tag;
164    require(ccder_decode_tag(&tag, der, der_end),fail);
165
166    switch (tag) {
167        case CCDER_OCTET_STRING:
168        {
169            der = der_decode_data(kCFAllocatorDefault, 0, &otr_data, error, der, der_end);
170            p->waitingForDataPacket = false;
171        }
172        break;
173
174        case CCDER_CONSTRUCTED_SEQUENCE:
175        {
176            const uint8_t *sequence_end = NULL;
177            der = ccder_decode_sequence_tl(&sequence_end, der, der_end);
178
179            require_action_quiet(sequence_end == der_end, fail, SecCFDERCreateError(kSOSErrorDecodeFailure, CFSTR("Extra data in SOS coder"), NULL, error));
180
181            der = der_decode_data(kCFAllocatorDefault, 0, &otr_data, error, der, der_end);
182            der = der_decode_bool(&p->waitingForDataPacket, der, sequence_end);
183        }
184        break;
185
186        default:
187            SecCFDERCreateError(kSOSErrorDecodeFailure, CFSTR("Unsupported SOS Coder DER"), NULL, error);
188            goto fail;
189    }
190
191    require(der, fail);
192
193    p->sessRef = SecOTRSessionCreateFromData(NULL, otr_data);
194    require(p->sessRef, fail);
195
196    CFReleaseSafe(otr_data);
197    return p;
198
199fail:
200    SOSCoderDispose(p);
201    CFReleaseSafe(otr_data);
202    return NULL;
203}
204
205
206SOSCoderRef SOSCoderCreate(SOSPeerInfoRef peerInfo, SOSFullPeerInfoRef myPeerInfo, CFErrorRef *error) {
207    CFAllocatorRef allocator = CFGetAllocator(peerInfo);
208
209    SOSCoderRef p = calloc(1, sizeof(struct __OpaqueSOSCoder));
210
211    SecOTRFullIdentityRef myRef = NULL;
212    SecOTRPublicIdentityRef peerRef = NULL;
213    SecKeyRef privateKey = NULL;
214    SecKeyRef publicKey = NULL;
215
216    if (myPeerInfo && peerInfo) {
217        privateKey = SOSFullPeerInfoCopyDeviceKey(myPeerInfo, error);
218        require_quiet(privateKey, errOut);
219
220        myRef = SecOTRFullIdentityCreateFromSecKeyRef(allocator, privateKey, error);
221        require_quiet(myRef, errOut);
222
223        CFReleaseNull(privateKey);
224
225        publicKey = SOSPeerInfoCopyPubKey(peerInfo);
226
227        peerRef = SecOTRPublicIdentityCreateFromSecKeyRef(allocator, publicKey, error);
228        require_quiet(peerRef, errOut);
229
230        p->sessRef = SecOTRSessionCreateFromID(allocator, myRef, peerRef);
231
232        require(p->sessRef, errOut);
233
234        p->waitingForDataPacket = false;
235
236        CFReleaseNull(publicKey);
237        CFReleaseNull(privateKey);
238        CFReleaseNull(myRef);
239        CFReleaseNull(peerRef);
240    } else {
241        secnotice("coder", "NULL Coder requested, no transport security");
242    }
243
244    return p;
245
246errOut:
247    secerror("Coder create failed: %@\n", *error);
248    CFReleaseNull(myRef);
249    CFReleaseNull(peerRef);
250    CFReleaseNull(publicKey);
251    CFReleaseNull(privateKey);
252
253    free(p);
254    return NULL;
255}
256
257void SOSCoderDispose(SOSCoderRef coder)
258{
259    CFReleaseNull(coder->sessRef);
260
261    free(coder);
262}
263
264void SOSCoderReset(SOSCoderRef coder)
265{
266    SecOTRSessionReset(coder->sessRef);
267    coder->waitingForDataPacket = false;
268}
269
270static bool SOSOTRSAppendStartPacket(SecOTRSessionRef session, CFMutableDataRef appendPacket, CFErrorRef *error) {
271    OSStatus otrStatus = SecOTRSAppendStartPacket(session, appendPacket);
272    if (otrStatus != errSecSuccess) {
273        SOSCreateErrorWithFormat(kSOSErrorEncodeFailure, (error != NULL) ? *error : NULL, error, NULL, CFSTR("append start packet returned: %" PRIdOSStatus), otrStatus);
274    }
275    return otrStatus == errSecSuccess;
276}
277
278// Start OTR negotiation if we haven't already done so.
279SOSPeerCoderStatus
280SOSCoderStart(SOSCoderRef coder, SOSPeerSendBlock sendBlock, CFStringRef clientId, CFErrorRef *error) {
281    CFMutableStringRef action = CFStringCreateMutable(kCFAllocatorDefault, 0);
282    CFStringRef beginState = NULL;
283    SOSPeerCoderStatus result = kSOSPeerCoderFailure;
284    CFMutableDataRef startPacket = NULL;
285
286    require_action_quiet(coder->sessRef, coderFailure, CFStringAppend(action, CFSTR("*** no otr session ***")));
287    beginState = CFCopyDescription(coder->sessRef);
288    require_action_quiet(!coder->waitingForDataPacket, negotiatingOut, CFStringAppend(action, CFSTR("waiting for peer to send first data packet")));
289    require_action_quiet(!SecOTRSGetIsReadyForMessages(coder->sessRef), coderFailure, CFStringAppend(action, CFSTR("otr session ready"));
290                         result = kSOSPeerCoderDataReturned);
291    require_action_quiet(SecOTRSGetIsIdle(coder->sessRef), negotiatingOut, CFStringAppend(action, CFSTR("otr negotiating already")));
292    require_action_quiet(startPacket = CFDataCreateMutable(kCFAllocatorDefault, 0), coderFailure, SOSCreateError(kSOSErrorAllocationFailure, CFSTR("alloc failed"), NULL, error));
293    require_quiet(SOSOTRSAppendStartPacket(coder->sessRef, startPacket, error), coderFailure);
294    require_quiet(sendBlock(startPacket, error), coderFailure);
295
296negotiatingOut:
297    result = kSOSPeerCoderNegotiating;
298coderFailure:
299    // Uber state log
300    if (result == kSOSPeerCoderFailure && error && *error)
301        CFStringAppendFormat(action, NULL, CFSTR(" %@"), *error);
302    secnotice("coder", "%@ %@ %s %@ %@ returned %s", clientId, beginState,
303              SecOTRPacketTypeString(startPacket), action, coder->sessRef, SOSPeerCoderString(result));
304    CFReleaseNull(startPacket);
305    CFReleaseSafe(beginState);
306    CFRelease(action);
307
308    return result;
309
310}
311
312SOSPeerCoderStatus
313SOSCoderResendDH(SOSCoderRef coder, SOSPeerSendBlock sendBlock, CFErrorRef *error) {
314    if(coder->sessRef == NULL) return kSOSPeerCoderDataReturned;
315
316    CFMutableDataRef startPacket = CFDataCreateMutable(kCFAllocatorDefault, 0);
317    if (SecOTRSAppendRestartPacket(coder->sessRef, startPacket)) {
318        return kSOSPeerCoderFailure;
319    }
320
321    secnotice("coder", "Resending OTR Start %@", startPacket);
322    SOSPeerCoderStatus result = sendBlock(startPacket, error) ? kSOSPeerCoderNegotiating : kSOSPeerCoderFailure;
323    CFReleaseNull(startPacket);
324    return result;
325}
326
327
328static SOSPeerCoderStatus nullCoder(CFDataRef from, CFMutableDataRef *to) {
329    *to = CFDataCreateMutableCopy(NULL, CFDataGetLength(from), from);
330    return kSOSPeerCoderDataReturned;
331}
332
333SOSPeerCoderStatus SOSCoderUnwrap(SOSCoderRef coder, SOSPeerSendBlock send_block,
334                                  CFDataRef codedMessage, CFMutableDataRef *message,
335                                  CFStringRef clientId,
336                                  CFErrorRef *error) {
337    if(codedMessage == NULL) return kSOSPeerCoderDataReturned;
338    if(coder->sessRef == NULL) return nullCoder(codedMessage, message);
339    CFMutableStringRef action = CFStringCreateMutable(kCFAllocatorDefault, 0);
340    /* This should be the "normal" case.  We just use OTR to unwrap the received message. */
341    SOSPeerCoderStatus result = kSOSPeerCoderFailure;
342
343    CFStringRef beginState = CFCopyDescription(coder->sessRef);
344    enum SecOTRSMessageKind kind = SecOTRSGetMessageKind(coder->sessRef, codedMessage);
345
346    switch (kind) {
347        case kOTRNegotiationPacket: {
348            if(send_block) {
349                /* If we're in here we haven't completed negotiating a session.  Use SecOTRSProcessPacket() to go through
350                 the negotiation steps and immediately send a reply back if necessary using the sendBlock.  This
351                 assumes the sendBlock is still available.
352                 */
353                CFMutableDataRef response = CFDataCreateMutable(kCFAllocatorDefault, 0);
354                OSStatus ppstatus = errSecSuccess;
355                if (response) {
356                    switch (ppstatus = SecOTRSProcessPacket(coder->sessRef, codedMessage, response)) {
357                        case errSecSuccess:
358                            if (CFDataGetLength(response) > 1) {
359                                CFStringAppendFormat(action, NULL, CFSTR("Sending OTR Response %s"), SecOTRPacketTypeString(response));
360                                if (send_block(response, error)) {
361                                    result = kSOSPeerCoderNegotiating;
362                                    if (SecOTRSGetIsReadyForMessages(coder->sessRef)) {
363                                        CFStringAppend(action, CFSTR(" begin waiting for data packet"));
364                                        coder->waitingForDataPacket = true;
365                                    }
366                                } else {
367                                    secerror("%@ Coder send Error %@", clientId, (CFTypeRef)error);
368                                    result =  kSOSPeerCoderFailure;
369                                }
370                            } else if(!SecOTRSGetIsReadyForMessages(coder->sessRef)) {
371                                CFStringAppend(action, CFSTR("stuck?"));
372                                result = kSOSPeerCoderNegotiating;
373                            } else {
374                                CFStringAppend(action, CFSTR("completed negotiation"));
375                                result = kSOSPeerCoderNegotiationCompleted;
376                                coder->waitingForDataPacket = false;
377                            }
378                            break;
379                        case errSecDecode:
380                            CFStringAppend(action, CFSTR("resending dh"));
381                            result = SOSCoderResendDH(coder, send_block, error);
382                            break;
383                        default:
384                            SOSCreateErrorWithFormat(kSOSErrorEncodeFailure, (error != NULL) ? *error : NULL, error, NULL, CFSTR("%@ Cannot negotiate session (%ld)"), clientId, (long)ppstatus);
385                            result = kSOSPeerCoderFailure;
386                            break;
387                    };
388                } else {
389                    SOSCreateErrorWithFormat(kSOSErrorAllocationFailure, (error != NULL) ? *error : NULL, error, NULL, CFSTR("%@ Cannot allocate CFData"), clientId);
390                    result = kSOSPeerCoderFailure;
391                }
392
393                CFReleaseNull(response);
394            } else {
395                secerror("%@ Can't send, no send_block!!", clientId);
396                SOSCreateErrorWithFormat(kSOSErrorEncodeFailure, (error != NULL) ? *error : NULL, error, NULL, CFSTR("%@ Cannot negotiate session"), clientId);
397                result = kSOSPeerCoderFailure;
398            }
399
400            break;
401        }
402
403        case kOTRDataPacket:
404            if(!SecOTRSGetIsReadyForMessages(coder->sessRef)) {
405                CFStringAppend(action, CFSTR("not ready, resending DH packet"));
406				SetCloudKeychainTraceValueForKey(kCloudKeychainNumberOfTimesSyncFailed, 1);
407                CFStringAppend(action, CFSTR("not ready for data; resending dh"));
408                result = SOSCoderResendDH(coder, send_block, error);
409            } else {
410                if (coder->waitingForDataPacket) {
411                    CFStringAppend(action, CFSTR("got data packet we were waiting for "));
412                    coder->waitingForDataPacket = false;
413                }
414                CFMutableDataRef exposed = CFDataCreateMutable(0, 0);
415                OSStatus otrResult = SecOTRSVerifyAndExposeMessage(coder->sessRef, codedMessage, exposed);
416                CFStringAppend(action, CFSTR("verify and expose message"));
417                if (otrResult) {
418                    if (otrResult == errSecOTRTooOld) {
419                        CFStringAppend(action, CFSTR(" too old"));
420                        result = kSOSPeerCoderStaleEvent;
421                    } else {
422                        SecError(otrResult, error, CFSTR("%@ Cannot expose message: %" PRIdOSStatus), clientId, otrResult);
423                        secerror("%@ Decode OTR Protected Packet: %@", clientId, error ? *error : NULL);
424                        result = kSOSPeerCoderFailure;
425                    }
426                } else {
427                    CFStringAppend(action, CFSTR("decoded OTR protected packet"));
428                    *message = exposed;
429                    exposed = NULL;
430                    result = kSOSPeerCoderDataReturned;
431                }
432                CFReleaseNull(exposed);
433            }
434            break;
435
436        default:
437            secerror("%@ Unknown packet type: %@", clientId, codedMessage);
438            SOSCreateError(kSOSErrorDecodeFailure, CFSTR("Unknown packet type"), (error != NULL) ? *error : NULL, error);
439            result = kSOSPeerCoderFailure;
440            break;
441    };
442
443    // Uber state log
444    if (result == kSOSPeerCoderFailure && error && *error)
445        CFStringAppendFormat(action, NULL, CFSTR(" %@"), *error);
446    secnotice("coder", "%@ %@ %s %@ %@ returned %s", clientId, beginState,
447              SecOTRPacketTypeString(codedMessage), action, coder->sessRef, SOSPeerCoderString(result));
448    CFReleaseSafe(beginState);
449    CFRelease(action);
450
451    return result;
452}
453
454
455SOSPeerCoderStatus SOSCoderWrap(SOSCoderRef coder, CFDataRef message, CFMutableDataRef *codedMessage, CFStringRef clientId, CFErrorRef *error) {
456    CFMutableStringRef action = CFStringCreateMutable(kCFAllocatorDefault, 0);
457    SOSPeerCoderStatus result = kSOSPeerCoderDataReturned;
458    CFStringRef beginState = NULL;
459    CFMutableDataRef encoded = NULL;
460    OSStatus otrStatus = 0;
461
462    require_action_quiet(coder->sessRef, errOut,
463                         CFStringAppend(action, CFSTR("*** using null coder ***"));
464                         result = nullCoder(message, codedMessage));
465    beginState = CFCopyDescription(coder->sessRef);
466    require_action_quiet(SecOTRSGetIsReadyForMessages(coder->sessRef), errOut,
467                         CFStringAppend(action, CFSTR("not ready"));
468                         result = kSOSPeerCoderNegotiating);
469    require_action_quiet(!coder->waitingForDataPacket, errOut,
470                         CFStringAppend(action, CFSTR("waiting for peer to send data packet first"));
471                         result = kSOSPeerCoderNegotiating);
472    require_action_quiet(encoded = CFDataCreateMutable(kCFAllocatorDefault, 0), errOut,
473                         SOSCreateErrorWithFormat(kSOSErrorAllocationFailure, NULL, error, NULL, CFSTR("%@ alloc failed"), clientId);
474                         result = kSOSPeerCoderFailure);
475    require_noerr_action_quiet(otrStatus = SecOTRSSignAndProtectMessage(coder->sessRef, message, encoded), errOut,
476                               SOSCreateErrorWithFormat(kSOSErrorEncodeFailure, (error != NULL) ? *error : NULL, error, NULL, CFSTR("%@ cannot protect message: %" PRIdOSStatus), clientId, otrStatus);
477                               CFReleaseNull(encoded);
478                               result = kSOSPeerCoderFailure);
479    *codedMessage = encoded;
480
481errOut:
482    // Uber state log
483    if (result == kSOSPeerCoderFailure && error && *error)
484        CFStringAppendFormat(action, NULL, CFSTR(" %@"), *error);
485    secnotice("coder", "%@ %@ %s %@ %@ returned %s", clientId, beginState,
486              SecOTRPacketTypeString(encoded), action, coder->sessRef, SOSPeerCoderString(result));
487    CFReleaseSafe(beginState);
488    CFRelease(action);
489
490    return result;
491}
492
493bool SOSCoderCanWrap(SOSCoderRef coder) {
494    return coder->sessRef && SecOTRSGetIsReadyForMessages(coder->sessRef) && !coder->waitingForDataPacket;
495}
496