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