Fix CFB and OFB modes in eng_padlock.c. Engine was consistent with itself,
authorAndy Polyakov <appro@openssl.org>
Wed, 28 Dec 2005 16:16:56 +0000 (16:16 +0000)
committerAndy Polyakov <appro@openssl.org>
Wed, 28 Dec 2005 16:16:56 +0000 (16:16 +0000)
but not interoperable with the rest of the world. test_padlock script is
added mostly for reference.

crypto/engine/eng_padlock.c
test/test_padlock [new file with mode: 0755]

index 1ba0d4302a437a7d20ba3a15628b2cb89881259c..9519c54afbdeb9fcb3ea4d9e8a74eee1efb62944 100644 (file)
@@ -257,7 +257,9 @@ struct padlock_cipher_data
        union { unsigned int pad[4];
                struct {
                        int rounds:4;
-                       int algo:3;
+                       int dgst:1;     /* n/a in C3 */
+                       int align:1;    /* n/a in C3 */
+                       int ciphr:1;    /* n/a in C3 */
                        int keygen:1;
                        int interm:1;
                        int encdec:1;
@@ -650,17 +652,13 @@ static int padlock_cipher_nids[] = {
 
        NID_aes_192_ecb,
        NID_aes_192_cbc,
-#if 0
-       NID_aes_192_cfb,        /* FIXME: AES192/256 CFB/OFB don't work. */
+       NID_aes_192_cfb,
        NID_aes_192_ofb,
-#endif
 
        NID_aes_256_ecb,
        NID_aes_256_cbc,
-#if 0
        NID_aes_256_cfb,
        NID_aes_256_ofb,
-#endif
 };
 static int padlock_cipher_nids_num = (sizeof(padlock_cipher_nids)/
                                      sizeof(padlock_cipher_nids[0]));
@@ -676,12 +674,17 @@ static int padlock_aes_cipher(EVP_CIPHER_CTX *ctx, unsigned char *out,
 #define ALIGNED_CIPHER_DATA(ctx) ((struct padlock_cipher_data *)\
        NEAREST_ALIGNED(ctx->cipher_data))
 
+#define EVP_CIPHER_block_size_ECB      AES_BLOCK_SIZE
+#define EVP_CIPHER_block_size_CBC      AES_BLOCK_SIZE
+#define EVP_CIPHER_block_size_OFB      1
+#define EVP_CIPHER_block_size_CFB      1
+
 /* Declaring so many ciphers by hand would be a pain.
    Instead introduce a bit of preprocessor magic :-) */
 #define        DECLARE_AES_EVP(ksize,lmode,umode)      \
 static const EVP_CIPHER padlock_aes_##ksize##_##lmode = {      \
        NID_aes_##ksize##_##lmode,              \
-       AES_BLOCK_SIZE,                 \
+       EVP_CIPHER_block_size_##umode,  \
        AES_KEY_SIZE_##ksize,           \
        AES_BLOCK_SIZE,                 \
        0 | EVP_CIPH_##umode##_MODE,    \
@@ -783,7 +786,10 @@ padlock_aes_init_key (EVP_CIPHER_CTX *ctx, const unsigned char *key,
        memset(cdata, 0, sizeof(struct padlock_cipher_data));
 
        /* Prepare Control word. */
-       cdata->cword.b.encdec = (ctx->encrypt == 0);
+       if (EVP_CIPHER_CTX_mode(ctx) == EVP_CIPH_OFB_MODE)
+               cdata->cword.b.encdec = 0;
+       else
+               cdata->cword.b.encdec = (ctx->encrypt == 0);
        cdata->cword.b.rounds = 10 + (key_len - 128) / 32;
        cdata->cword.b.ksize = (key_len - 128) / 64;
 
@@ -803,7 +809,9 @@ padlock_aes_init_key (EVP_CIPHER_CTX *ctx, const unsigned char *key,
                           and is listed as hardware errata. They most
                           likely will fix it at some point and then
                           a check for stepping would be due here. */
-                       if (enc)
+                       if (EVP_CIPHER_CTX_mode(ctx) == EVP_CIPH_CFB_MODE ||
+                           EVP_CIPHER_CTX_mode(ctx) == EVP_CIPH_OFB_MODE ||
+                           enc)
                                AES_set_encrypt_key(key, key_len, &cdata->ks);
                        else
                                AES_set_decrypt_key(key, key_len, &cdata->ks);
@@ -897,10 +905,53 @@ padlock_aes_cipher(EVP_CIPHER_CTX *ctx, unsigned char *out_arg,
        int    inp_misaligned, out_misaligned, realign_in_loop;
        size_t chunk, allocated=0;
 
+       /* ctx->num is maintained in byte-oriented modes,
+          such as CFB and OFB... */
+       if ((chunk = ctx->num)) { /* borrow chunk variable */
+               unsigned char *ivp=ctx->iv;
+
+               switch (EVP_CIPHER_CTX_mode(ctx)) {
+               case EVP_CIPH_CFB_MODE:
+                       if (chunk >= AES_BLOCK_SIZE)
+                               return 0; /* bogus value */
+
+                       if (ctx->encrypt)
+                               while (chunk<AES_BLOCK_SIZE && nbytes!=0) {
+                                       ivp[chunk] = *(out_arg++) = *(in_arg++) ^ ivp[chunk];
+                                       chunk++, nbytes--;
+                               }
+                       else    while (chunk<AES_BLOCK_SIZE && nbytes!=0) {
+                                       unsigned char c = *(in_arg++);
+                                       *(out_arg++) = c ^ ivp[chunk];
+                                       ivp[chunk++] = c, nbytes--;
+                               }
+
+                       ctx->num = chunk%AES_BLOCK_SIZE;
+                       break;
+               case EVP_CIPH_OFB_MODE:
+                       if (chunk >= AES_BLOCK_SIZE)
+                               return 0; /* bogus value */
+
+                       while (chunk<AES_BLOCK_SIZE && nbytes!=0) {
+                               *(out_arg++) = *(in_arg++) ^ ivp[chunk];
+                               chunk++, nbytes--;
+                       }
+
+                       ctx->num = chunk%AES_BLOCK_SIZE;
+                       break;
+               }
+       }
+
        if (nbytes == 0)
                return 1;
+#if 0
        if (nbytes % AES_BLOCK_SIZE)
                return 0; /* are we expected to do tail processing? */
+#else
+       /* nbytes is always multiple of AES_BLOCK_SIZE in ECB and CBC
+          modes and arbitrary value in byte-oriented modes, such as
+          CFB and OFB... */
+#endif
 
        /* VIA promises CPUs that won't require alignment in the future.
           For now padlock_aes_align_required is initialized to 1 and
@@ -910,7 +961,7 @@ padlock_aes_cipher(EVP_CIPHER_CTX *ctx, unsigned char *out_arg,
           same as for software alignment below or ~3x. They promise to
           improve it in the future, but for now we can just as well
           pretend that it can only handle aligned input... */
-       if (!padlock_aes_align_required)
+       if (!padlock_aes_align_required && (nbytes%AES_BLOCK_SIZE)==0)
                return padlock_aes_cipher_omnivorous(ctx, out_arg, in_arg, nbytes);
 
        inp_misaligned = (((size_t)in_arg) & 0x0F);
@@ -922,7 +973,7 @@ padlock_aes_cipher(EVP_CIPHER_CTX *ctx, unsigned char *out_arg,
         * in order to improve L1 cache utilization... */
        realign_in_loop = out_misaligned|inp_misaligned;
 
-       if (!realign_in_loop)
+       if (!realign_in_loop && (nbytes%AES_BLOCK_SIZE)==0)
                return padlock_aes_cipher_omnivorous(ctx, out_arg, in_arg, nbytes);
 
        /* this takes one "if" out of the loops */
@@ -989,8 +1040,10 @@ padlock_aes_cipher(EVP_CIPHER_CTX *ctx, unsigned char *out_arg,
                break;
 
        case EVP_CIPH_CFB_MODE:
-               memcpy (cdata->iv, ctx->iv, AES_BLOCK_SIZE);
-               goto cfb_shortcut;
+               memcpy (iv = cdata->iv, ctx->iv, AES_BLOCK_SIZE);
+               chunk &= ~(AES_BLOCK_SIZE-1);
+               if (chunk)      goto cfb_shortcut;
+               else            goto cfb_skiploop;
                do      {
                        if (iv != cdata->iv)
                                memcpy(cdata->iv, iv, AES_BLOCK_SIZE);
@@ -1009,13 +1062,47 @@ padlock_aes_cipher(EVP_CIPHER_CTX *ctx, unsigned char *out_arg,
                        else
                                out     = out_arg+=chunk;
 
-               } while (nbytes -= chunk);
+                       nbytes -= chunk;
+               } while (nbytes >= AES_BLOCK_SIZE);
+
+               cfb_skiploop:
+               if (nbytes) {
+                       unsigned char *ivp = cdata->iv;
+
+                       if (iv != ivp) {
+                               memcpy(ivp, iv, AES_BLOCK_SIZE);
+                               iv = ivp;
+                       }
+                       ctx->num = nbytes;
+                       if (cdata->cword.b.encdec) {
+                               cdata->cword.b.encdec=0;
+                               padlock_reload_key();
+                               padlock_xcrypt_ecb(1,cdata,ivp,ivp);
+                               cdata->cword.b.encdec=1;
+                               padlock_reload_key();
+                               while(nbytes) {
+                                       unsigned char c = *(in_arg++);
+                                       *(out_arg++) = c ^ *ivp;
+                                       *(ivp++) = c, nbytes--;
+                               }
+                       }
+                       else {  padlock_reload_key();
+                               padlock_xcrypt_ecb(1,cdata,ivp,ivp);
+                               padlock_reload_key();
+                               while (nbytes) {
+                                       *ivp = *(out_arg++) = *(in_arg++) ^ *ivp;
+                                       ivp++, nbytes--;
+                               }
+                       }
+               }
+
                memcpy(ctx->iv, iv, AES_BLOCK_SIZE);
                break;
 
        case EVP_CIPH_OFB_MODE:
                memcpy(cdata->iv, ctx->iv, AES_BLOCK_SIZE);
-               do      {
+               chunk &= ~(AES_BLOCK_SIZE-1);
+               if (chunk) do   {
                        if (inp_misaligned)
                                inp = padlock_memcpy(out, in_arg, chunk);
                        else
@@ -1031,7 +1118,21 @@ padlock_aes_cipher(EVP_CIPHER_CTX *ctx, unsigned char *out_arg,
 
                        nbytes -= chunk;
                        chunk   = PADLOCK_CHUNK;
-               } while (nbytes);
+               } while (nbytes >= AES_BLOCK_SIZE);
+
+               if (nbytes) {
+                       unsigned char *ivp = cdata->iv;
+
+                       ctx->num = nbytes;
+                       padlock_reload_key();   /* empirically found */
+                       padlock_xcrypt_ecb(1,cdata,ivp,ivp);
+                       padlock_reload_key();   /* empirically found */
+                       while (nbytes) {
+                               *(out_arg++) = *(in_arg++) ^ *ivp;
+                               ivp++, nbytes--;
+                       }
+               }
+
                memcpy(ctx->iv, cdata->iv, AES_BLOCK_SIZE);
                break;
 
diff --git a/test/test_padlock b/test/test_padlock
new file mode 100755 (executable)
index 0000000..6dd4911
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+PROG=$1
+
+if [ -x $PROG ]; then
+    if expr "x`$PROG version`" : "xOpenSSL" > /dev/null; then
+       :
+    else
+       echo "$PROG is not OpenSSL executable"
+       exit 1
+    fi
+else
+    echo "$PROG is not executable"
+    exit 1;
+fi
+
+if $PROG engine padlock | grep -v no-ACE; then
+
+    HASH=`cat $PROG | $PROG dgst -hex`
+
+    ACE_ALGS=" aes-128-ecb aes-192-ecb aes-256-ecb \
+               aes-128-cbc aes-192-cbc aes-256-cbc \
+               aes-128-cfb aes-192-cfb aes-256-cfb \
+               aes-128-ofb aes-192-ofb aes-256-ofb"
+
+    nerr=0
+
+    for alg in $ACE_ALGS; do
+       echo $alg
+       TEST=`( cat $PROG | \
+               $PROG enc -e -k $HASH -$alg -bufsize 999 -engine padlock | \
+               $PROG enc -d -k $HASH -$alg | \
+               $PROG dgst -hex ) 2>/dev/null`
+       if [ $TEST != $HASH ]; then
+               echo "-$alg encrypt test failed"
+               nerr=`expr $nerr + 1`
+       fi
+       TEST=`( cat $PROG | \
+               $PROG enc -e -k $HASH -$alg | \
+               $PROG enc -d -k $HASH -$alg -bufsize 999 -engine padlock | \
+               $PROG dgst -hex ) 2>/dev/null`
+       if [ $TEST != $HASH ]; then
+               echo "-$alg decrypt test failed"
+               nerr=`expr $nerr + 1`
+       fi
+       TEST=`( cat $PROG | \
+               $PROG enc -e -k $HASH -$alg -engine padlock | \
+               $PROG enc -d -k $HASH -$alg -engine padlock | \
+               $PROG dgst -hex ) 2>/dev/null`
+       if [ $TEST != $HASH ]; then
+               echo "-$alg en/decrypt test failed"
+               nerr=`expr $nerr + 1`
+       fi
+    done
+
+    if [ $nerr -gt 0 ]; then
+       echo "PadLock ACE test failed."
+       exit 1;
+    fi
+else
+    echo "PadLock ACE is not available"
+fi
+
+exit 0