2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please obtain
7 * a copy of the License at http://www.apple.com/publicsource and read it before
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
15 * specific language governing rights and limitations under the License.
22 Contains: Record encrypting/decrypting/MACing for SSL 2
24 Written by: Doug Mitchell
26 Copyright: (c) 1999 by Apple Computer, Inc., all rights reserved.
31 #include "sslRecord.h"
32 #include "sslMemory.h"
33 #include "sslContext.h"
34 #include "sslAlertMessage.h"
37 #include "sslDigests.h"
41 static OSStatus
SSL2DecryptRecord(
44 static OSStatus
SSL2VerifyMAC(
48 static OSStatus
SSL2CalculateMAC(
52 const HashReference
&hash
,
58 SSL2ReadRecord(SSLRecord
&rec
, SSLContext
*ctx
)
60 UInt32 len
, contentLen
;
61 int padding
, headerSize
;
63 SSLBuffer readData
, cipherFragment
;
65 switch (ctx
->negProtocolVersion
)
66 { case SSL_Version_Undetermined
:
67 case SSL_Version_3_0_With_2_0_Hello
:
70 case SSL_Version_3_0
: /* We've negotiated a 3.0 session;
71 * we can send an alert */
73 SSLFatalSessionAlert(SSL_AlertUnexpectedMsg
, ctx
);
74 return errSSLProtocol
;
75 case SSL_Version_3_0_Only
: /* We haven't yet negotiated, but
76 * we don't want to support 2.0; just
77 * die without an alert */
78 return errSSLProtocol
;
80 sslErrorLog("bad protocolVersion in ctx->protocolVersion");
81 return errSSLInternal
;
84 if (!ctx
->partialReadBuffer
.data
|| ctx
->partialReadBuffer
.length
< 3)
85 { if (ctx
->partialReadBuffer
.data
)
86 if ((err
= SSLFreeBuffer(ctx
->partialReadBuffer
, ctx
)) != 0)
87 { SSLFatalSessionAlert(SSL_AlertCloseNotify
, ctx
);
90 if ((err
= SSLAllocBuffer(ctx
->partialReadBuffer
, DEFAULT_BUFFER_SIZE
, ctx
)) != 0)
91 { SSLFatalSessionAlert(SSL_AlertCloseNotify
, ctx
);
96 if (ctx
->amountRead
< 3)
97 { readData
.length
= 3 - ctx
->amountRead
;
98 readData
.data
= ctx
->partialReadBuffer
.data
+ ctx
->amountRead
;
99 len
= readData
.length
;
100 err
= sslIoRead(readData
, &len
, ctx
);
102 { if (err
== errSSLWouldBlock
)
103 ctx
->amountRead
+= len
;
104 if (err
== ioErr
&& ctx
->amountRead
== 0) /* If the session closes on a record boundary, it's graceful */
105 err
= errSSLClosedGraceful
;
108 ctx
->amountRead
+= len
;
111 rec
.contentType
= SSL_RecordTypeV2_0
;
112 rec
.protocolVersion
= SSL_Version_2_0
;
113 charPtr
= ctx
->partialReadBuffer
.data
;
115 if (((*charPtr
) & 0x80) != 0) /* High bit on -> specifies 2-byte header */
117 contentLen
= ((charPtr
[0] & 0x7F) << 8) | charPtr
[1];
120 else if (((*charPtr
) & 0x40) != 0) /* Bit 6 on -> specifies security escape */
121 { return errSSLProtocol
; /* No security escapes are defined */
123 else /* 3-byte header */
125 contentLen
= ((charPtr
[0] & 0x3F) << 8) | charPtr
[1];
126 padding
= charPtr
[2];
130 * FIXME - what's the max record size?
131 * and why doesn't SSLReadRecord parse the 2 or 3 byte header?
132 * Note: I see contentLen of 0 coming back from www.cduniverse.com when
133 * it's only been given SSL_RSA_EXPORT_WITH_DES40_CBC_SHA.
135 if((contentLen
== 0) || (contentLen
> 0xffff)) {
136 return errSSLProtocol
;
139 charPtr
+= headerSize
;
141 if (ctx
->partialReadBuffer
.length
< headerSize
+ contentLen
)
142 { if ((err
= SSLReallocBuffer(ctx
->partialReadBuffer
, 5 + contentLen
, ctx
)) != 0)
146 if (ctx
->amountRead
< headerSize
+ contentLen
)
147 { readData
.length
= headerSize
+ contentLen
- ctx
->amountRead
;
148 readData
.data
= ctx
->partialReadBuffer
.data
+ ctx
->amountRead
;
149 len
= readData
.length
;
150 err
= sslIoRead(readData
, &len
, ctx
);
152 { if (err
== errSSLWouldBlock
)
153 ctx
->amountRead
+= len
;
156 ctx
->amountRead
+= len
;
159 cipherFragment
.data
= ctx
->partialReadBuffer
.data
+ headerSize
;
160 cipherFragment
.length
= contentLen
;
161 if ((err
= SSL2DecryptRecord(cipherFragment
, ctx
)) != 0)
164 cipherFragment
.length
-= padding
; /* Remove padding; MAC was removed
165 * by SSL2DecryptRecord */
167 IncrementUInt64(&ctx
->readCipher
.sequenceNum
);
169 /* Allocate a buffer to return the plaintext in and return it */
170 if ((err
= SSLAllocBuffer(rec
.contents
, cipherFragment
.length
, ctx
)) != 0)
172 memcpy(rec
.contents
.data
, cipherFragment
.data
, cipherFragment
.length
);
174 ctx
->amountRead
= 0; /* We've used all the data in the cache */
180 SSL2WriteRecord(SSLRecord
&rec
, SSLContext
*ctx
)
182 int padding
= 0, i
, headerSize
;
183 WaitingRecord
*out
, *queue
;
184 SSLBuffer buf
, content
, payload
, secret
, mac
;
186 UInt16 payloadSize
, blockSize
;
188 assert(rec
.contents
.length
< 16384);
191 /* Allocate a WaitingRecord to store our ready-to-send record in */
192 if ((err
= SSLAllocBuffer(buf
, sizeof(WaitingRecord
), ctx
)) != 0)
194 out
= (WaitingRecord
*)buf
.data
;
198 payloadSize
= (UInt16
)
199 (rec
.contents
.length
+ ctx
->writeCipher
.macRef
->hash
->digestSize
);
200 blockSize
= ctx
->writeCipher
.symCipher
->blockSize
;
203 padding
= blockSize
- (payloadSize
% blockSize
);
204 if (padding
== blockSize
)
206 payloadSize
+= padding
;
214 if ((err
= SSLAllocBuffer(out
->data
, headerSize
+ payloadSize
, ctx
)) != 0)
216 charPtr
= out
->data
.data
;
219 charPtr
= SSLEncodeInt(charPtr
, payloadSize
| 0x8000, 2);
221 { charPtr
= SSLEncodeInt(charPtr
, payloadSize
, 2);
222 *charPtr
++ = padding
;
225 payload
.data
= charPtr
;
226 payload
.length
= payloadSize
;
229 mac
.length
= ctx
->writeCipher
.macRef
->hash
->digestSize
;
230 charPtr
+= mac
.length
;
232 content
.data
= charPtr
;
233 content
.length
= rec
.contents
.length
+ padding
;
234 memcpy(charPtr
, rec
.contents
.data
, rec
.contents
.length
);
235 charPtr
+= rec
.contents
.length
;
238 *charPtr
++ = padding
;
240 assert(charPtr
== out
->data
.data
+ out
->data
.length
);
242 secret
.data
= ctx
->writeCipher
.macSecret
;
243 secret
.length
= ctx
->writeCipher
.symCipher
->keySize
;
245 if ((err
= SSL2CalculateMAC(secret
, content
,
246 ctx
->writeCipher
.sequenceNum
.low
,
247 *ctx
->writeCipher
.macRef
->hash
, mac
, ctx
)) != 0)
250 if ((err
= ctx
->writeCipher
.symCipher
->encrypt(payload
,
256 /* Enqueue the record to be written from the idle loop */
257 if (ctx
->recordWriteQueue
== 0)
258 ctx
->recordWriteQueue
= out
;
260 { queue
= ctx
->recordWriteQueue
;
261 while (queue
->next
!= 0)
266 /* Increment the sequence number */
267 IncrementUInt64(&ctx
->writeCipher
.sequenceNum
);
273 * Only for if we fail between when the WaitingRecord is allocated and
276 SSLFreeBuffer(out
->data
, 0);
277 buf
.data
= (UInt8
*)out
;
278 buf
.length
= sizeof(WaitingRecord
);
279 SSLFreeBuffer(buf
, ctx
);
284 SSL2DecryptRecord(SSLBuffer
&payload
, SSLContext
*ctx
)
288 if (ctx
->readCipher
.symCipher
->blockSize
> 0)
289 if (payload
.length
% ctx
->readCipher
.symCipher
->blockSize
!= 0)
290 return errSSLProtocol
;
292 /* Decrypt in place */
293 if ((err
= ctx
->readCipher
.symCipher
->decrypt(payload
,
299 if (ctx
->readCipher
.macRef
->hash
->digestSize
> 0)
300 /* Optimize away MAC for null case */
301 { content
.data
= payload
.data
+ ctx
->readCipher
.macRef
->hash
->digestSize
; /* Data is after MAC */
302 content
.length
= payload
.length
- ctx
->readCipher
.macRef
->hash
->digestSize
;
303 if ((err
= SSL2VerifyMAC(content
, payload
.data
, ctx
)) != 0)
305 /* Adjust payload to remove MAC; caller is still responsible
306 * for removing padding [if any] */
313 #define IGNORE_MAC_FAILURE 0
316 SSL2VerifyMAC(SSLBuffer
&content
, UInt8
*compareMAC
, SSLContext
*ctx
)
318 UInt8 calculatedMAC
[SSL_MAX_DIGEST_LEN
];
319 SSLBuffer secret
, mac
;
321 secret
.data
= ctx
->readCipher
.macSecret
;
322 secret
.length
= ctx
->readCipher
.symCipher
->keySize
;
323 mac
.data
= calculatedMAC
;
324 mac
.length
= ctx
->readCipher
.macRef
->hash
->digestSize
;
325 if ((err
= SSL2CalculateMAC(secret
, content
, ctx
->readCipher
.sequenceNum
.low
,
326 *ctx
->readCipher
.macRef
->hash
, mac
, ctx
)) != 0)
328 if (memcmp(mac
.data
, compareMAC
, mac
.length
) != 0) {
329 #if IGNORE_MAC_FAILURE
330 sslErrorLog("SSL2VerifyMAC: Mac verify failure\n");
333 sslErrorLog("SSL2VerifyMAC: Mac verify failure\n");
334 return errSSLProtocol
;
340 #define LOG_MAC_DATA 0
342 static void logMacData(
348 printf("%s: ", field
);
349 for(i
=0; i
<data
->length
; i
++) {
350 printf("%02X", data
->data
[i
]);
357 #else /* LOG_MAC_DATA */
358 #define logMacData(f, d)
359 #endif /* LOG_MAC_DATA */
361 /* For SSL 2, the MAC is hash ( secret || content || sequence# )
362 * where secret is the decryption key for the message, content is
363 * the record data plus any padding used to round out the record
364 * size to an even multiple of the block size and sequence# is
365 * a monotonically increasing 32-bit unsigned integer.
372 const HashReference
&hash
,
376 UInt8 sequenceNum
[4];
377 SSLBuffer seqData
, hashContext
;
379 SSLEncodeInt(sequenceNum
, seqNo
, 4);
380 seqData
.data
= sequenceNum
;
383 hashContext
.data
= 0;
384 if ((err
= ReadyHash(hash
, hashContext
, ctx
)) != 0)
386 if ((err
= hash
.update(hashContext
, secret
)) != 0)
388 if ((err
= hash
.update(hashContext
, content
)) != 0)
390 if ((err
= hash
.update(hashContext
, seqData
)) != 0)
392 if ((err
= hash
.final(hashContext
, mac
)) != 0)
395 logMacData("secret ", &secret
);
396 logMacData("seqData", &seqData
);
397 logMacData("mac ", &mac
);
401 SSLFreeBuffer(hashContext
, ctx
);
406 SSL2SendError(SSL2ErrorCode error
, SSLContext
*ctx
)
411 rec
.contentType
= SSL_RecordTypeV2_0
;
412 rec
.protocolVersion
= SSL_Version_2_0
;
413 rec
.contents
.data
= errorData
;
414 rec
.contents
.length
= 3;
415 errorData
[0] = SSL2_MsgError
;
416 SSLEncodeInt(errorData
+ 1, error
, 2);
418 err
= SSL2WriteRecord(rec
, ctx
);