source: box/trunk/lib/crypto/CipherContext.cpp @ 3097

Revision 3097, 17.5 KB checked in by chris, 4 weeks ago (diff)

Log errors from OpenSSL and clear the error queue to avoid bad state.

  • Property svn:eol-style set to native
Line 
1// --------------------------------------------------------------------------
2//
3// File
4//              Name:    CipherContext.cpp
5//              Purpose: Context for symmetric encryption / descryption
6//              Created: 1/12/03
7//
8// --------------------------------------------------------------------------
9
10#include "Box.h"
11
12#define BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_TRUE
13#include "CipherContext.h"
14#include "CipherDescription.h"
15#include "CipherException.h"
16#include "CryptoUtils.h"
17#include "Random.h"
18
19#include "MemLeakFindOn.h"
20
21// --------------------------------------------------------------------------
22//
23// Function
24//              Name:    CipherContext::CipherContext()
25//              Purpose: Constructor
26//              Created: 1/12/03
27//
28// --------------------------------------------------------------------------
29CipherContext::CipherContext()
30: mInitialised(false),
31  mWithinTransform(false),
32  mPaddingOn(true),
33  mFunction(None)
34#ifdef HAVE_OLD_SSL
35, mpDescription(0)
36#endif
37{
38}
39
40// --------------------------------------------------------------------------
41//
42// Function
43//              Name:    CipherContext::~CipherContext()
44//              Purpose: Destructor
45//              Created: 1/12/03
46//
47// --------------------------------------------------------------------------
48CipherContext::~CipherContext()
49{
50        if(mInitialised)
51        {
52                // Clean up
53                EVP_CIPHER_CTX_cleanup(&ctx);
54                mInitialised = false;
55        }
56#ifdef HAVE_OLD_SSL
57        if(mpDescription != 0)
58        {
59                delete mpDescription;
60                mpDescription = 0;
61        }
62#endif
63}
64
65// --------------------------------------------------------------------------
66//
67// Function
68//              Name:    CipherContext::LogError(const std::string& operation)
69//              Purpose: Logs and clears any OpenSSL errors, returning the
70//                       most recent error message for use in exception
71//                       messages.
72//
73//                       It's essential to clear the OpenSSL error queue after
74//                       ANY failed OpenSSL operation, because OpenSSL may
75//                       decide that a later non-blocking read (returning -1
76//                       with errno == EAGAIN) is actually an error if there's
77//                       any errors left in the queue. See SSL_get_error
78//                       (called from SocketStreamTLS::Read) for the details.
79//              Created: 26/04/12
80//
81// --------------------------------------------------------------------------
82std::string CipherContext::LogError(const std::string& operation)
83{
84        return CryptoUtils::LogError(operation);
85}
86
87// --------------------------------------------------------------------------
88//
89// Function
90//              Name:    CipherContext::Init(CipherContext::CipherFunction, const CipherDescription &)
91//              Purpose: Initialises the context, specifying the direction for the encryption, and a
92//                               description of the cipher to use, it's keys, etc
93//              Created: 1/12/03
94//
95// --------------------------------------------------------------------------
96void CipherContext::Init(CipherContext::CipherFunction Function, const CipherDescription &rDescription)
97{
98        // Check for bad usage
99        if(mInitialised)
100        {
101                THROW_EXCEPTION(CipherException, AlreadyInitialised)
102        }
103        if(Function != Decrypt && Function != Encrypt)
104        {
105                THROW_EXCEPTION(CipherException, BadArguments)
106        }
107       
108        // Store function for later
109        mFunction = Function;
110
111        // Initialise the cipher
112#ifndef HAVE_OLD_SSL
113        EVP_CIPHER_CTX_init(&ctx); // no error return code, even though the docs says it does
114
115        if(EVP_CipherInit_ex(&ctx, rDescription.GetCipher(), NULL, NULL, NULL,
116                (mFunction == Encrypt) ? 1 : 0) != 1)
117#else
118        // Use old version of init call
119        if(EVP_CipherInit(&ctx, rDescription.GetCipher(), NULL, NULL,
120                (mFunction == Encrypt) ? 1 : 0) != 1)
121#endif
122        {
123                THROW_EXCEPTION_MESSAGE(CipherException, EVPInitFailure,
124                        "Failed to initialise " << rDescription.GetFullName()
125                        << "cipher: " << LogError("initialising cipher"));
126        }
127       
128        try
129        {
130                mCipherName = rDescription.GetFullName();
131#ifndef HAVE_OLD_SSL
132                // Let the description set up everything else
133                rDescription.SetupParameters(&ctx);
134#else
135                // With the old version, a copy needs to be taken first.
136                mpDescription = rDescription.Clone();
137                // Mark it as not a leak, otherwise static cipher contexts
138                // cause spurious memory leaks to be reported
139                MEMLEAKFINDER_NOT_A_LEAK(mpDescription);
140                mpDescription->SetupParameters(&ctx);
141#endif
142        }
143        catch(...)
144        {
145                THROW_EXCEPTION_MESSAGE(CipherException, EVPInitFailure,
146                        "Failed to configure " << mCipherName << " cipher: " <<
147                        LogError("configuring cipher"));
148                EVP_CIPHER_CTX_cleanup(&ctx);
149                throw;
150        }
151
152        // mark as initialised
153        mInitialised = true;
154}
155
156// --------------------------------------------------------------------------
157//
158// Function
159//              Name:    CipherContext::Reset()
160//              Purpose: Reset the context, so it can be initialised again with a different key
161//              Created: 1/12/03
162//
163// --------------------------------------------------------------------------
164void CipherContext::Reset()
165{
166        if(mInitialised)
167        {
168                // Clean up
169                EVP_CIPHER_CTX_cleanup(&ctx);
170                mInitialised = false;
171        }
172#ifdef HAVE_OLD_SSL
173        if(mpDescription != 0)
174        {
175                delete mpDescription;
176                mpDescription = 0;
177        }
178#endif
179        mWithinTransform = false;
180}
181
182
183// --------------------------------------------------------------------------
184//
185// Function
186//              Name:    CipherContext::Begin()
187//              Purpose: Begin a transformation
188//              Created: 1/12/03
189//
190// --------------------------------------------------------------------------
191void CipherContext::Begin()
192{
193        if(!mInitialised)
194        {
195                THROW_EXCEPTION(CipherException, NotInitialised)
196        }
197
198        // Warn if in a transformation (not an error, because a context might not have been finalised if an exception occured)
199        if(mWithinTransform)
200        {
201                BOX_WARNING("CipherContext::Begin called when context "
202                        "flagged as within a transform");
203        }
204
205        // Initialise the cipher context again
206        if(EVP_CipherInit(&ctx, NULL, NULL, NULL, -1) != 1)
207        {
208                THROW_EXCEPTION_MESSAGE(CipherException, EVPInitFailure,
209                        "Failed to reset " << mCipherName << " cipher: " <<
210                        LogError("resetting cipher"));
211        }
212       
213        // Mark as being within a transform
214        mWithinTransform = true;
215}
216
217
218// --------------------------------------------------------------------------
219//
220// Function
221//              Name:    CipherContext::Transform(void *, int, const void *, int)
222//              Purpose: Transforms the data in the in buffer to the out buffer. If pInBuffer == 0 && InLength == 0
223//                               then Final() is called instead.
224//                               Returns the number of bytes placed in the out buffer.
225//                               There must be room in the out buffer for all the data in the in buffer.
226//              Created: 1/12/03
227//
228// --------------------------------------------------------------------------
229int CipherContext::Transform(void *pOutBuffer, int OutLength, const void *pInBuffer, int InLength)
230{
231        if(!mInitialised)
232        {
233                THROW_EXCEPTION(CipherException, NotInitialised)
234        }
235       
236        if(!mWithinTransform)
237        {
238                THROW_EXCEPTION(CipherException, BeginNotCalled)
239        }
240
241        // Check parameters
242        if(pOutBuffer == 0 || OutLength < 0 || (pInBuffer != 0 && InLength <= 0) || (pInBuffer == 0 && InLength != 0))
243        {
244                THROW_EXCEPTION(CipherException, BadArguments)
245        }
246       
247        // Is this the final call?
248        if(pInBuffer == 0)
249        {
250                return Final(pOutBuffer, OutLength);
251        }
252       
253        // Check output buffer size
254        if(OutLength < (InLength + EVP_CIPHER_CTX_block_size(&ctx)))
255        {
256                THROW_EXCEPTION(CipherException, OutputBufferTooSmall);
257        }
258       
259        // Do the transform
260        int outLength = OutLength;
261        if(EVP_CipherUpdate(&ctx, (unsigned char*)pOutBuffer, &outLength, (unsigned char*)pInBuffer, InLength) != 1)
262        {
263                THROW_EXCEPTION_MESSAGE(CipherException, EVPUpdateFailure,
264                        "Failed to " << GetFunction() << " (update) " <<
265                        mCipherName << " cipher: " << LogError(GetFunction()));
266        }
267
268        return outLength;
269}
270
271
272// --------------------------------------------------------------------------
273//
274// Function
275//              Name:    CipherContext::Final(void *, int)
276//              Purpose: Transforms the data as per Transform, and returns the final data in the out buffer.
277//                               Returns the number of bytes written in the out buffer.
278//                               Two main causes of exceptions being thrown: 1) Data is corrupt, and so the end isn't
279//                               padded properly. 2) Padding is off, and the data to be encrypted isn't a multiple
280//                               of a block long.
281//              Created: 1/12/03
282//
283// --------------------------------------------------------------------------
284int CipherContext::Final(void *pOutBuffer, int OutLength)
285{
286        if(!mInitialised)
287        {
288                THROW_EXCEPTION(CipherException, NotInitialised)
289        }
290
291        if(!mWithinTransform)
292        {
293                THROW_EXCEPTION(CipherException, BeginNotCalled)
294        }
295
296        // Check parameters
297        if(pOutBuffer == 0 || OutLength < 0)
298        {
299                THROW_EXCEPTION(CipherException, BadArguments)
300        }
301
302        // Check output buffer size
303        if(OutLength < (2 * EVP_CIPHER_CTX_block_size(&ctx)))
304        {
305                THROW_EXCEPTION(CipherException, OutputBufferTooSmall);
306        }
307       
308        // Do the transform
309        int outLength = OutLength;
310#ifndef HAVE_OLD_SSL
311        if(EVP_CipherFinal(&ctx, (unsigned char*)pOutBuffer, &outLength) != 1)
312        {
313                mWithinTransform = false;
314                THROW_EXCEPTION_MESSAGE(CipherException, EVPFinalFailure,
315                        "Failed to " << GetFunction() << " (final) " <<
316                        mCipherName << " cipher: " << LogError(GetFunction()));
317        }
318#else
319        OldOpenSSLFinal((unsigned char*)pOutBuffer, outLength);
320#endif
321       
322        mWithinTransform = false;
323
324        return outLength;
325}
326
327
328#ifdef HAVE_OLD_SSL
329// --------------------------------------------------------------------------
330//
331// Function
332//              Name:    CipherContext::OldOpenSSLFinal(unsigned char *, int &)
333//              Purpose: The old version of OpenSSL needs more work doing to finalise the cipher,
334//                               and reset it so that it's ready for another go.
335//              Created: 27/3/04
336//
337// --------------------------------------------------------------------------
338void CipherContext::OldOpenSSLFinal(unsigned char *Buffer, int &rOutLengthOut)
339{
340        // Old version needs to use a different form, and then set up the cipher again for next time around
341        int outLength = rOutLengthOut;
342        // Have to emulate padding off...
343        int blockSize = EVP_CIPHER_CTX_block_size(&ctx);
344        if(mPaddingOn)
345        {
346                // Just use normal final call
347                if(EVP_CipherFinal(&ctx, Buffer, &outLength) != 1)
348                {
349                        THROW_EXCEPTION(CipherException, EVPFinalFailure)
350                }
351        }
352        else
353        {
354                // Padding is off. OpenSSL < 0.9.7 doesn't support this, so it has to be
355                // bodged in there. Which isn't nice.
356                if(mFunction == Decrypt)
357                {
358                        // NASTY -- fiddling around with internals like this is bad.
359                        // But only way to get this working on old versions of OpenSSL.
360                        if(!EVP_EncryptUpdate(&ctx,Buffer,&outLength,ctx.buf,0)
361                                || outLength != blockSize)
362                        {
363                                THROW_EXCEPTION(CipherException, EVPFinalFailure)
364                        }
365                        // Clean up
366                        EVP_CIPHER_CTX_cleanup(&ctx);
367                }
368                else
369                {
370                        // Check that the length is correct
371                        if((ctx.buf_len % blockSize) != 0)
372                        {
373                                THROW_EXCEPTION(CipherException, EVPFinalFailure)
374                        }
375                        // For encryption, assume that the last block entirely is
376                        // padding, and remove it.
377                        char temp[1024];
378                        outLength = sizeof(temp);
379                        if(EVP_CipherFinal(&ctx, Buffer, &outLength) != 1)
380                        {
381                                THROW_EXCEPTION(CipherException, EVPFinalFailure)
382                        }
383                        // Remove last block, assuming it's full of padded bytes only.
384                        outLength -= blockSize;
385                        // Copy anything to the main buffer
386                        // (can't just use main buffer, because it might overwrite something important)
387                        if(outLength > 0)
388                        {
389                                ::memcpy(Buffer, temp, outLength);
390                        }
391                }
392        }
393        // Reinitialise the cipher for the next time around
394        if(EVP_CipherInit(&ctx, mpDescription->GetCipher(), NULL, NULL,
395                (mFunction == Encrypt) ? 1 : 0) != 1)
396        {
397                THROW_EXCEPTION(CipherException, EVPInitFailure)
398        }
399        mpDescription->SetupParameters(&ctx);
400
401        // Update length for caller
402        rOutLengthOut = outLength;
403}
404#endif
405
406// --------------------------------------------------------------------------
407//
408// Function
409//              Name:    CipherContext::InSizeForOutBufferSize(int)
410//              Purpose: Returns the maximum amount of data that can be sent in
411//                               given a output buffer size.
412//              Created: 1/12/03
413//
414// --------------------------------------------------------------------------
415int CipherContext::InSizeForOutBufferSize(int OutLength)
416{
417        if(!mInitialised)
418        {
419                THROW_EXCEPTION(CipherException, NotInitialised)
420        }
421
422        // Strictly speaking, the *2 is unnecessary. However...
423        // Final() is paranoid, and requires two input blocks of space to work.
424        return OutLength - (EVP_CIPHER_CTX_block_size(&ctx) * 2);
425}
426
427// --------------------------------------------------------------------------
428//
429// Function
430//              Name:    CipherContext::MaxOutSizeForInBufferSize(int)
431//              Purpose: Returns the maximum output size for an input of a given length.
432//                               Will tend to over estimate, as it needs to allow space for Final() to be called.
433//              Created: 3/12/03
434//
435// --------------------------------------------------------------------------
436int CipherContext::MaxOutSizeForInBufferSize(int InLength)
437{
438        if(!mInitialised)
439        {
440                THROW_EXCEPTION(CipherException, NotInitialised)
441        }
442
443        // Final() is paranoid, and requires two input blocks of space to work, and so we need to add
444        // three blocks on to be absolutely sure.
445        return InLength + (EVP_CIPHER_CTX_block_size(&ctx) * 3);
446}
447
448
449// --------------------------------------------------------------------------
450//
451// Function
452//              Name:    CipherContext::TransformBlock(void *, int, const void *, int)
453//              Purpose: Transform one block to another all in one go, no Final required.
454//              Created: 1/12/03
455//
456// --------------------------------------------------------------------------
457int CipherContext::TransformBlock(void *pOutBuffer, int OutLength, const void *pInBuffer, int InLength)
458{
459        if(!mInitialised)
460        {
461                THROW_EXCEPTION(CipherException, NotInitialised)
462        }
463       
464        // Warn if in a transformation
465        if(mWithinTransform)
466        {
467                BOX_WARNING("CipherContext::TransformBlock called when "
468                        "context flagged as within a transform");
469        }
470
471        // Check output buffer size
472        if(OutLength < (InLength + EVP_CIPHER_CTX_block_size(&ctx)))
473        {
474                // Check if padding is off, in which case the buffer can be smaller
475                if(!mPaddingOn && OutLength <= InLength)
476                {
477                        // This is OK.
478                }
479                else
480                {
481                        THROW_EXCEPTION(CipherException, OutputBufferTooSmall);
482                }
483        }
484       
485        // Initialise the cipher context again
486        if(EVP_CipherInit(&ctx, NULL, NULL, NULL, -1) != 1)
487        {
488                THROW_EXCEPTION(CipherException, EVPInitFailure)
489        }
490       
491        // Do the entire block
492        int outLength = 0;
493
494        // Update
495        outLength = OutLength;
496        if(EVP_CipherUpdate(&ctx, (unsigned char*)pOutBuffer, &outLength, (unsigned char*)pInBuffer, InLength) != 1)
497        {
498                THROW_EXCEPTION_MESSAGE(CipherException, EVPUpdateFailure,
499                        "Failed to " << GetFunction() << " (update) " <<
500                        mCipherName << " cipher: " << LogError(GetFunction()));
501        }
502
503        // Finalise
504        int outLength2 = OutLength - outLength;
505#ifndef HAVE_OLD_SSL
506        if(EVP_CipherFinal(&ctx, ((unsigned char*)pOutBuffer) + outLength, &outLength2) != 1)
507        {
508                THROW_EXCEPTION_MESSAGE(CipherException, EVPFinalFailure,
509                        "Failed to " << GetFunction() << " (final) " <<
510                        mCipherName << " cipher: " << LogError(GetFunction()));
511        }
512#else
513        OldOpenSSLFinal(((unsigned char*)pOutBuffer) + outLength, outLength2);
514#endif
515        outLength += outLength2;
516
517        return outLength;
518}
519
520
521// --------------------------------------------------------------------------
522//
523// Function
524//              Name:    CipherContext::GetIVLength()
525//              Purpose: Returns the size of the IV for this context
526//              Created: 3/12/03
527//
528// --------------------------------------------------------------------------
529int CipherContext::GetIVLength()
530{
531        if(!mInitialised)
532        {
533                THROW_EXCEPTION(CipherException, NotInitialised)
534        }
535       
536        return EVP_CIPHER_CTX_iv_length(&ctx);
537}
538
539
540// --------------------------------------------------------------------------
541//
542// Function
543//              Name:    CipherContext::SetIV(const void *)
544//              Purpose: Sets the IV for this context (must be correctly sized, use GetIVLength)
545//              Created: 3/12/03
546//
547// --------------------------------------------------------------------------
548void CipherContext::SetIV(const void *pIV)
549{
550        if(!mInitialised)
551        {
552                THROW_EXCEPTION(CipherException, NotInitialised)
553        }
554       
555        // Warn if in a transformation
556        if(mWithinTransform)
557        {
558                BOX_WARNING("CipherContext::SetIV called when context "
559                        "flagged as within a transform");
560        }
561
562        // Set IV
563        if(EVP_CipherInit(&ctx, NULL, NULL, (unsigned char *)pIV, -1) != 1)
564        {
565                THROW_EXCEPTION_MESSAGE(CipherException, EVPInitFailure,
566                        "Failed to " << GetFunction() << " (set IV) " <<
567                        mCipherName << " cipher: " << LogError(GetFunction()));
568        }
569
570#ifdef HAVE_OLD_SSL
571        // Update description
572        if(mpDescription != 0)
573        {
574                mpDescription->SetIV(pIV);
575        }
576#endif
577}
578
579
580// --------------------------------------------------------------------------
581//
582// Function
583//              Name:    CipherContext::SetRandomIV(int &)
584//              Purpose: Set a random IV for the context, and return a pointer to the IV used,
585//                               and the length of this IV in the rLengthOut arg.
586//              Created: 3/12/03
587//
588// --------------------------------------------------------------------------
589const void *CipherContext::SetRandomIV(int &rLengthOut)
590{
591        if(!mInitialised)
592        {
593                THROW_EXCEPTION(CipherException, NotInitialised)
594        }
595       
596        // Warn if in a transformation
597        if(mWithinTransform)
598        {
599                BOX_WARNING("CipherContext::SetRandomIV called when "
600                        "context flagged as within a transform");
601        }
602
603        // Get length of IV
604        unsigned int ivLen = EVP_CIPHER_CTX_iv_length(&ctx);
605        if(ivLen > sizeof(mGeneratedIV))
606        {
607                THROW_EXCEPTION(CipherException, IVSizeImplementationLimitExceeded)
608        }
609       
610        // Generate some random data
611        Random::Generate(mGeneratedIV, ivLen);
612        SetIV(mGeneratedIV);
613
614        // Return the IV and it's length
615        rLengthOut = ivLen;
616        return mGeneratedIV;
617}
618
619
620// --------------------------------------------------------------------------
621//
622// Function
623//              Name:    CipherContext::UsePadding(bool)
624//              Purpose: Set whether or not the context uses padding.
625//              Created: 12/12/03
626//
627// --------------------------------------------------------------------------
628void CipherContext::UsePadding(bool Padding)
629{
630#ifndef HAVE_OLD_SSL
631        if(EVP_CIPHER_CTX_set_padding(&ctx, Padding) != 1)
632        {
633                THROW_EXCEPTION(CipherException, EVPSetPaddingFailure)
634        }
635#endif
636        mPaddingOn = Padding;
637}
638
639
640
Note: See TracBrowser for help on using the repository browser.