2 * Copyright (c) 2011-2014 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
25 #include "SecOTRSession.h"
27 #include "SecOTRMath.h"
28 #include "SecOTRIdentityPriv.h"
29 #include "SecOTRSessionPriv.h"
30 #include "SecOTRPackets.h"
31 #include "SecOTRPacketData.h"
32 #include "SecOTRDHKey.h"
34 #include <utilities/SecCFWrappers.h>
35 #include <utilities/SecBuffer.h>
37 #include <CoreFoundation/CFRuntime.h>
38 #include <CoreFoundation/CFString.h>
40 #include <Security/SecBase.h>
41 #include <Security/SecRandom.h>
43 #include <AssertMacros.h>
45 #include <corecrypto/cchmac.h>
46 #include <corecrypto/ccsha2.h>
48 #include <os/activity.h>
52 static void SecOTRInitMyDHKeys(SecOTRSessionRef session
)
55 CFReleaseNull(session
->_myKey
);
56 session
->_myKey
= SecOTRFullDHKCreate(kCFAllocatorDefault
);
57 CFReleaseNull(session
->_myNextKey
);
58 session
->_myNextKey
= SecOTRFullDHKCreate(kCFAllocatorDefault
);
60 session
->_missedAck
= true;
61 session
->_receivedAck
= false;
62 bzero(session
->_keyCache
, sizeof(session
->_keyCache
));
64 secnotice("otr", "%@ Reinitializing DH Keys, first: %@", session
, session
->_myKey
);
67 OSStatus
SecOTRSAppendStartPacket(SecOTRSessionRef session
, CFMutableDataRef appendPacket
)
69 __block OSStatus result
= errSecSuccess
;
71 dispatch_sync(session
->_queue
, ^{
72 session
->_state
= kAwaitingDHKey
;
74 // Generate r and x and calculate gx:
75 SecOTRInitMyDHKeys(session
);
77 CFMutableDataRef dhMessage
= CFDataCreateMutable(kCFAllocatorDefault
, 0);
79 result
= SecRandomCopyBytes(kSecRandomDefault
, sizeof(session
->_r
), session
->_r
);
80 if (result
== errSecSuccess
) {
81 SecOTRAppendDHMessage(session
, dhMessage
);
84 CFDataPerformWithHexString(dhMessage
, ^(CFStringRef messageString
) {
85 secnotice("otr", "%@ Start packet: %@", session
, messageString
);
88 if (session
->_textOutput
) {
89 SecOTRPrepareOutgoingBytes(dhMessage
, appendPacket
);
91 CFDataAppend(appendPacket
, dhMessage
);
94 CFReleaseSafe(dhMessage
);
101 OSStatus
SecOTRSAppendRestartPacket(SecOTRSessionRef session
, CFMutableDataRef appendPacket
)
103 __block OSStatus result
= errSecSuccess
;
105 dispatch_sync(session
->_queue
, ^{
106 if (!session
->_myKey
) {
107 secerror("_myKey is NULL, avoiding crash");
108 result
= errSecDecode
;
111 CFMutableDataRef dhMessage
= CFDataCreateMutable(kCFAllocatorDefault
, 0);
113 session
->_state
= kAwaitingDHKey
;
114 CFReleaseNull(session
->_receivedDHMessage
);
115 CFReleaseNull(session
->_receivedDHKeyMessage
);
117 SecOTRAppendDHMessage(session
, dhMessage
);
119 CFDataPerformWithHexString(dhMessage
, ^(CFStringRef messageString
) {
120 secnotice("otr", "%@ Restart packet: %@", session
, messageString
);
123 if (session
->_textOutput
) {
124 SecOTRPrepareOutgoingBytes(dhMessage
, appendPacket
);
126 CFDataAppend(appendPacket
, dhMessage
);
128 CFReleaseSafe(dhMessage
);
134 static const uint8_t* FindGXHash(CFDataRef dhPacket
)
136 const uint8_t* messageBytes
= CFDataGetBytePtr(dhPacket
);
137 size_t remainingBytes
= (size_t)CFDataGetLength(dhPacket
);
139 OTRMessageType messageType
;
141 require_noerr(ReadHeader(&messageBytes
, &remainingBytes
, &messageType
), fail
);
142 require(messageType
== kDHMessage
, fail
);
144 uint32_t egxiLength
= 0;
145 require_noerr(ReadLong(&messageBytes
, &remainingBytes
, & egxiLength
), fail
);
146 require(egxiLength
<= remainingBytes
, fail
);
147 messageBytes
+= egxiLength
;
148 remainingBytes
-= egxiLength
;
150 uint32_t dataLength
= 0;
151 require_noerr(ReadLong(&messageBytes
, &remainingBytes
, &dataLength
), fail
);
152 require(dataLength
<= remainingBytes
, fail
);
153 require(dataLength
== CCSHA256_OUTPUT_SIZE
, fail
);
161 static bool SecOTRMyGXHashIsBigger(SecOTRSessionRef session
, CFDataRef dhCommitMessage
)
163 bool mineIsBigger
= false;
165 CFMutableDataRef myDHCommitMessage
= CFDataCreateMutable(kCFAllocatorDefault
, 0);
167 SecOTRAppendDHMessage(session
, myDHCommitMessage
);
169 const uint8_t* myHash
= FindGXHash(myDHCommitMessage
);
170 const uint8_t* theirHash
= FindGXHash(dhCommitMessage
);
172 require(myHash
, fail
);
173 require(theirHash
, fail
);
175 mineIsBigger
= 0 < memcmp(myHash
, theirHash
, CCSHA256_OUTPUT_SIZE
);
177 BufferPerformWithHexString(myHash
, CCSHA256_OUTPUT_SIZE
, ^(CFStringRef myHashString
) {
178 BufferPerformWithHexString(theirHash
, CCSHA256_OUTPUT_SIZE
, ^(CFStringRef theirHashString
) {
179 secdebug("otr", "%@ %s gx is bigger, M:%@ T:%@", session
, mineIsBigger
? "mine" : "their", myHashString
, theirHashString
);
184 CFReleaseNull(myDHCommitMessage
);
188 static OSStatus
SecOTRSProcessDHMessage(SecOTRSessionRef session
,
189 CFDataRef incomingPacket
,
190 CFMutableDataRef negotiationResponse
)
192 OSStatus result
= errSecParam
;
194 CFStringRef messageMessage
= CFSTR("");
196 switch (session
->_state
) {
198 // Compare hash values.
199 if (SecOTRMyGXHashIsBigger(session
, incomingPacket
)) {
200 // If we're bigger we resend to force them to deal.
201 messageMessage
= CFSTR("Our GX is bigger, resending DH");
202 CFReleaseNull(session
->_receivedDHMessage
);
203 SecOTRAppendDHMessage(session
, negotiationResponse
);
204 result
= errSecSuccess
;
206 } // Else intentionally fall through to idle
207 messageMessage
= CFSTR("Our GX is bigger, resending DH");
208 case kAwaitingSignature
:
211 // Generate a new X and GX..
212 SecOTRInitMyDHKeys(session
);
213 // If we were already waiting on reveal, then just send the packet again
214 case kAwaitingRevealSignature
:
215 SecOTRAppendDHKeyMessage(session
, negotiationResponse
);
217 if (messageMessage
== 0)
218 messageMessage
= CFSTR("Sending DHKey");
219 // Keep the packet for use later.
220 CFReleaseNull(session
->_receivedDHMessage
);
221 session
->_receivedDHMessage
= CFDataCreateCopy(kCFAllocatorDefault
, incomingPacket
);
223 session
->_state
= kAwaitingRevealSignature
;
224 result
= errSecSuccess
;
227 result
= errSecInteractionNotAllowed
;
231 if (result
== errSecSuccess
) {
232 CFDataPerformWithHexString(negotiationResponse
, ^(CFStringRef responseString
) {
233 secnotice("otr", "%@ %@: %@", session
, messageMessage
, responseString
);
236 secnotice("otr", "%@ Process DH failed %d", session
, (int)result
);
241 static OSStatus
SecOTRSetupTheirKeyFrom(SecOTRSessionRef session
, const uint8_t**data
, size_t*size
)
243 SecOTRPublicDHKeyRef tempKey
= SecOTRPublicDHKCreateFromSerialization(kCFAllocatorDefault
, data
, size
);
244 require(tempKey
!= NULL
, fail
);
246 return SecOTRSetupInitialRemoteKey(session
, tempKey
);
252 static OSStatus
SecOTRSExtractTheirPublicDHKey(SecOTRSessionRef session
, CFDataRef dhPacket
)
254 OSStatus result
= errSecParam
;
256 const uint8_t *messageBytes
= CFDataGetBytePtr(dhPacket
);
257 size_t messageSize
= (size_t)CFDataGetLength(dhPacket
);
258 OTRMessageType messageType
= kDHMessage
; // Suppress warning.
260 ReadHeader(&messageBytes
, &messageSize
, &messageType
);
261 require(messageType
== kDHKeyMessage
, exit
);
263 result
= SecOTRSetupTheirKeyFrom(session
, &messageBytes
, &messageSize
);
270 static OSStatus
SecOTRSProcessDHKeyMessage(SecOTRSessionRef session
,
271 CFDataRef incomingPacket
,
272 CFMutableDataRef negotiationResponse
)
274 OSStatus result
= errSecUnimplemented
;
275 CFStringRef messageMessage
= CFSTR("");
277 result
= SecOTRSExtractTheirPublicDHKey(session
, incomingPacket
);
278 require_noerr(result
, exit
);
280 switch (session
->_state
) {
282 CFReleaseNull(session
->_receivedDHKeyMessage
);
283 SecOTRAppendRevealSignatureMessage(session
, negotiationResponse
);
284 session
->_state
= kAwaitingSignature
;
285 session
->_receivedDHKeyMessage
= CFDataCreateCopy(kCFAllocatorDefault
, incomingPacket
);
286 result
= errSecSuccess
;
287 messageMessage
= CFSTR("Sending reveal signature");
289 case kAwaitingSignature
:
290 if (CFEqualSafe(incomingPacket
, session
->_receivedDHKeyMessage
)) {
291 SecOTRAppendRevealSignatureMessage(session
, negotiationResponse
);
292 messageMessage
= CFSTR("Resending reveal signature");
294 messageMessage
= CFSTR("Ignoring new DHKey message");
296 result
= errSecSuccess
;
300 case kAwaitingRevealSignature
:
301 result
= errSecSuccess
;
302 messageMessage
= CFSTR("Ignoring DHKey message");
305 result
= errSecInteractionNotAllowed
;
310 if (result
== errSecSuccess
) {
311 CFDataPerformWithHexString(negotiationResponse
, ^(CFStringRef responseString
) {
312 secnotice("otr", "%@ %@: %@", session
, messageMessage
, responseString
);
315 secnotice("otr", "%@ Process DH failed %d", session
, (int)result
);
322 static OSStatus
SecOTRSExtractR(SecOTRSessionRef session
,
323 const uint8_t **messageBytes
,
326 OSStatus result
= errSecDecode
;
328 OTRMessageType messageType
= kDHMessage
; // Suppress warning
330 ReadHeader(messageBytes
, messageSize
, &messageType
);
331 require(messageType
== kRevealSignatureMessage
, exit
);
335 ReadLong(messageBytes
, messageSize
, &rSize
);
336 require(rSize
== kOTRAuthKeyBytes
, exit
);
339 memcpy(session
->_r
, *messageBytes
, kOTRAuthKeyBytes
);
341 *messageBytes
+= kOTRAuthKeyBytes
;
342 *messageSize
-= kOTRAuthKeyBytes
;
344 result
= errSecSuccess
;
349 static OSStatus
FindEncGYInDHPacket(SecOTRSessionRef session
,
350 const uint8_t **dhMessageBytesPtr
,
351 size_t *messageSizePtr
,
352 size_t* encGYBufferSize
)
354 OSStatus result
= errSecParam
;
355 require_action(*encGYBufferSize
>= kExponentiationBytes
+ 4, exit
, result
= errSecParam
);
357 OTRMessageType messageType
;
358 result
= ReadHeader(dhMessageBytesPtr
, messageSizePtr
, &messageType
);
359 require_noerr(result
, exit
);
360 require_action(messageType
== kDHMessage
, exit
, result
= errSecDecode
);
362 uint32_t readEncSize
;
363 result
= ReadLong(dhMessageBytesPtr
, messageSizePtr
, &readEncSize
);
364 require_noerr(result
, exit
);
366 *encGYBufferSize
= readEncSize
;
368 // Don't bother erasing the public gy decrypted, it's public after all.
373 static OSStatus
SecOTRSExtractRAndTheirDHKey(SecOTRSessionRef session
,
374 const uint8_t **messageBytes
,
377 OSStatus result
= errSecDecode
;
379 require(session
->_receivedDHMessage
!= NULL
, exit
);
380 result
= SecOTRSExtractR(session
, messageBytes
, messageSize
);
381 require_noerr(result
, exit
);
383 uint8_t gxiDecrypted
[kExponentiationBytes
+ 4];
384 const uint8_t *gxiDecryptedBuffer
= gxiDecrypted
;
386 const uint8_t* dhMessageBytes
= CFDataGetBytePtr(session
->_receivedDHMessage
);
387 size_t dhMessageSize
= (size_t)CFDataGetLength(session
->_receivedDHMessage
);
389 size_t encGYSize
= sizeof(gxiDecrypted
);
390 result
= FindEncGYInDHPacket(session
, &dhMessageBytes
, &dhMessageSize
, &encGYSize
);
391 require_noerr(result
, exit
);
392 require_action(encGYSize
<= kExponentiationBytes
+ 4, exit
, result
= errSecDecode
);
394 AES_CTR_IV0_Transform(sizeof(session
->_r
), session
->_r
, encGYSize
, dhMessageBytes
, gxiDecrypted
);
396 result
= SecOTRSetupTheirKeyFrom(session
, &gxiDecryptedBuffer
, &encGYSize
);
399 // Don't bother erasing the public gy decrypted, it's public after all.
403 static OSStatus
SecVerifySignatureAndMac(SecOTRSessionRef session
,
405 const uint8_t **signatureAndMacBytes
,
406 size_t *signatureAndMacSize
)
408 __block OSStatus result
= errSecDecode
;
410 PerformWithBufferAndClear(kOTRAuthMACKeyBytes
, ^(size_t m1_size
, uint8_t *m1
) {
411 PerformWithBufferAndClear(kOTRAuthMACKeyBytes
, ^(size_t m2_size
, uint8_t *m2
) {
412 PerformWithBufferAndClear(kOTRAuthKeyBytes
, ^(size_t c_size
, uint8_t *c
) {
414 cc_unit s
[kExponentiationUnits
];
416 SecPDHKeyGenerateS(session
->_myKey
, session
->_theirKey
, s
);
417 // Derive M1, M2 and C, either prime or normal versions.
418 DeriveOTR256BitsFromS(usePrimes
? kM1Prime
: kM1
,
419 kExponentiationUnits
, s
, m1_size
, m1
);
420 DeriveOTR256BitsFromS(usePrimes
? kM2Prime
: kM2
,
421 kExponentiationUnits
, s
, m2_size
, m2
);
422 DeriveOTR128BitPairFromS(kCs
,
423 kExponentiationUnits
, s
,
424 c_size
,usePrimes
? NULL
: c
,
425 c_size
, usePrimes
? c
: NULL
);
429 const uint8_t* encSigDataBlobStart
= *signatureAndMacBytes
;
432 result
= ReadLong(signatureAndMacBytes
, signatureAndMacSize
, &xbSize
);
433 require_noerr(result
, exit
);
434 require_action(xbSize
> 4, exit
, result
= errSecDecode
);
435 require_action(xbSize
<= *signatureAndMacSize
, exit
, result
= errSecDecode
);
437 uint8_t signatureMac
[CCSHA256_OUTPUT_SIZE
];
438 cchmac(ccsha256_di(), m2_size
, m2
, xbSize
+ 4, encSigDataBlobStart
, signatureMac
);
440 require_action(xbSize
+ kSHA256HMAC160Bytes
<= *signatureAndMacSize
, exit
, result
= errSecDecode
);
441 const uint8_t *macStart
= *signatureAndMacBytes
+ xbSize
;
443 // check the outer hmac
444 require_action(0 == cc_cmp_safe(kSHA256HMAC160Bytes
, macStart
, signatureMac
), exit
, result
= errSecDecode
);
447 PerformWithBufferAndClear(xbSize
, ^(size_t size
, uint8_t *xb
) {
448 cchmac_di_decl(ccsha256_di(), mBContext
);
450 cchmac_init(ccsha256_di(), mBContext
, m1_size
, m1
);
453 CFMutableDataRef toHash
= CFDataCreateMutable(kCFAllocatorDefault
, 0);
455 SecPDHKAppendSerialization(session
->_theirKey
, toHash
);
456 SecFDHKAppendPublicSerialization(session
->_myKey
, toHash
);
458 cchmac_update(ccsha256_di(), mBContext
, (size_t)CFDataGetLength(toHash
), CFDataGetBytePtr(toHash
));
460 CFReleaseNull(toHash
);
463 // Decrypt and copy the signature block
464 AES_CTR_IV0_Transform(c_size
, c
, xbSize
, *signatureAndMacBytes
, xb
);
466 const uint8_t* signaturePacket
= xb
;
467 size_t signaturePacketSize
= xbSize
;
470 result
= ReadShort(&signaturePacket
, &signaturePacketSize
, &pubKeyType
);
471 require_noerr(result
, exit
);
472 require_action(pubKeyType
== 0xF000, exit
, result
= errSecUnimplemented
);
475 result
= ReadLong(&signaturePacket
, &signaturePacketSize
, &pubKeySize
);
476 require_noerr(result
, exit
);
477 require_action(pubKeySize
<= signaturePacketSize
, exit
, result
= errSecDecode
);
478 require(((CFIndex
)pubKeySize
) >= 0, exit
);
480 // Add the signature and keyid to the hash.
481 // PUBKEY of our type is 2 bytes of type, 2 bytes of size and size bytes.
482 // Key ID is 4 bytes.
483 cchmac_update(ccsha256_di(), mBContext
, 2 + 4 + pubKeySize
+ 4, xb
);
485 uint8_t mb
[CCSHA256_OUTPUT_SIZE
];
486 cchmac_final(ccsha256_di(), mBContext
, mb
);
488 // Make reference to the deflated key
489 require_action(SecOTRPIEqualToBytes(session
->_them
, signaturePacket
, (CFIndex
)pubKeySize
), exit
, result
= errSecAuthFailed
);
491 signaturePacket
+= pubKeySize
;
492 signaturePacketSize
-= pubKeySize
;
494 result
= ReadLong(&signaturePacket
, &signaturePacketSize
, &session
->_theirKeyID
);
495 require_noerr(result
, exit
);
498 result
= ReadLong(&signaturePacket
, &signaturePacketSize
, &sigSize
);
499 require_noerr(result
, exit
);
500 require_action(sigSize
<= signaturePacketSize
, exit
, result
= errSecDecode
);
502 bool bresult
= SecOTRPIVerifySignature(session
->_them
, mb
, sizeof(mb
), signaturePacket
, sigSize
, NULL
);
503 result
= bresult
? errSecSuccess
: errSecDecode
;
504 require_noerr(result
, exit
);
518 static OSStatus
SecOTRSProcessRevealSignatureMessage(SecOTRSessionRef session
,
519 CFDataRef incomingPacket
,
520 CFMutableDataRef negotiationResponse
)
522 OSStatus result
= errSecParam
;
524 require_action_quiet(session
->_state
== kAwaitingRevealSignature
, exit
, result
= errSecSuccess
);
526 const uint8_t *messageBytes
= CFDataGetBytePtr(incomingPacket
);
527 size_t messageSize
= (size_t)CFDataGetLength(incomingPacket
);
529 result
= SecOTRSExtractRAndTheirDHKey(session
, &messageBytes
, &messageSize
);
530 require_noerr(result
, exit
);
532 result
= SecVerifySignatureAndMac(session
, false, &messageBytes
, &messageSize
);
533 require_noerr(result
, exit
);
535 SecOTRAppendSignatureMessage(session
, negotiationResponse
);
537 session
->_state
= kDone
;
538 result
= errSecSuccess
;
540 CFDataPerformWithHexString(negotiationResponse
, ^(CFStringRef responseString
) {
541 secnotice("otr", "%@ Sending Signature message: %@", session
, responseString
);
546 if (result
!= errSecSuccess
) {
547 CFDataPerformWithHexString(incomingPacket
, ^(CFStringRef incomingString
) {
548 secnotice("otr", "%@ Failed to process reveal sig message (%d): %@", session
, (int)result
, incomingString
);
554 static OSStatus
SecOTRSProcessSignatureMessage(SecOTRSessionRef session
,
555 CFDataRef incomingPacket
,
556 CFMutableDataRef negotiationResponse
)
558 OSStatus result
= errSecParam
;
560 require_action_quiet(session
->_state
== kAwaitingSignature
, exit
, result
= errSecSuccess
);
562 const uint8_t *messageBytes
= CFDataGetBytePtr(incomingPacket
);
563 size_t messageSize
= (size_t)CFDataGetLength(incomingPacket
);
565 OTRMessageType messageType
;
566 result
= ReadHeader(&messageBytes
, &messageSize
, &messageType
);
567 require_noerr(result
, exit
);
568 require_action(messageType
== kSignatureMessage
, exit
, result
= errSecDecode
);
570 result
= SecVerifySignatureAndMac(session
, true, &messageBytes
, &messageSize
);
571 require_noerr(result
, exit
);
573 CFReleaseNull(session
->_receivedDHKeyMessage
);
574 session
->_state
= kDone
;
576 result
= errSecSuccess
;
581 OSStatus
SecOTRSProcessPacket(SecOTRSessionRef session
,
582 CFDataRef incomingPacket
,
583 CFMutableDataRef negotiationResponse
)
585 __block OSStatus result
= errSecParam
;
587 require(CFDataGetLength(incomingPacket
) > 0, fail
);
588 dispatch_sync(session
->_queue
, ^{
589 os_activity_initiate("OTR Process Packet", OS_ACTIVITY_FLAG_DEFAULT
, ^{
590 CFDataRef decodedBytes
= SecOTRCopyIncomingBytes(incomingPacket
);
592 const uint8_t* bytes
= CFDataGetBytePtr(decodedBytes
);
593 size_t size
= CFDataGetLength(decodedBytes
);
595 OTRMessageType packetType
= kInvalidMessage
;
596 if (ReadHeader(&bytes
, &size
, &packetType
))
597 packetType
= kInvalidMessage
;
599 CFMutableDataRef destinationMessage
;
600 if (session
->_textOutput
) {
601 destinationMessage
= CFDataCreateMutable(kCFAllocatorDefault
, 0);
603 destinationMessage
= CFRetainSafe(negotiationResponse
);
606 switch (packetType
) {
608 result
= SecOTRSProcessDHMessage(session
, decodedBytes
, destinationMessage
);
611 result
= SecOTRSProcessDHKeyMessage(session
, decodedBytes
, destinationMessage
);
613 case kRevealSignatureMessage
:
614 result
= SecOTRSProcessRevealSignatureMessage(session
, decodedBytes
, destinationMessage
);
616 case kSignatureMessage
:
617 result
= SecOTRSProcessSignatureMessage(session
, decodedBytes
, destinationMessage
);
620 result
= errSecDecode
;
624 if (result
!= errSecSuccess
) {
625 CFDataPerformWithHexString(decodedBytes
, ^(CFStringRef bytesString
) {
626 secnotice("session", "%@ Error %d processing packet type %d, session state %d, keyid %d, myKey %p, myNextKey %p, theirKeyId %d, theirKey %p, theirPreviousKey %p, bytes %@", session
, (int)result
, packetType
, session
->_state
, session
->_keyID
, session
->_myKey
, session
->_myNextKey
, session
->_theirKeyID
, session
->_theirKey
, session
->_theirPreviousKey
, bytesString
);
631 if (session
->_textOutput
) {
632 SecOTRPrepareOutgoingBytes(destinationMessage
, negotiationResponse
);
634 CFReleaseSafe(destinationMessage
);
635 CFReleaseSafe(decodedBytes
);