EuCrypt Chapter 12: Wrapper C-Ada for RSA + OAEP

March 1st, 2018

~ This is part of the EuCrypt series. Start with Introducing EuCrypt. ~

Find yourself a comfortable position, as this might take a while to go through. At 1034 lines, the .vpatch for this chapter deals with the gnarly mess that is the passing of char* to Ada and back, while also gathering everything from previous chapters together into the actual EuCrypt library as a standalone, static lib. As usual and as always in my books, adding something does not mean that I'm taking away previous, useful options, quite on the contrary. Specifically, this chapter provides a way to compile everything in a single lib *in addition* to the previous options of compiling each and any of the EuCrypt components as standalone libraries if preferred.

The main thorny issue with compiling EuCrypt as a whole is that one needs to put together code in both Ada and C. I have to say that this past week increased my disgust for C at an incredible speed - I suspect this outcome is simply unavoidable when getting back to C after a spell of Ada and moreover when this getting back to C involves a significant part of in-your-face comparison of the 2 languages as one has to write a "wrapper for C" with 2 parameters for each 1 single parameter that Ada requires, while also being anyway at the mercy of the caller for all that verbosity to actually make any sense as intended. Still, as long as there isn't any replacement for the despicable mpi that effectively forces C into EuCrypt, such is the life to live - stinky, verbose and treacherous, so watch your step and read 10 times before running code even once, not to mention even thinking of writing any of it.

The brighter side of all this come of course from the Ada-part: GNAT's Project Manager (GPR) is an excellent tool for building and managing precisely this sort of mixed-languages projects. Moreover, GPR1 is the only tool that I found to actually work2 for aggregate libraries such as EuCrypt. And since this is the only thing that I know to actually work as intended for the task at hand, the first part of this .vpatch nukes all previous Makefiles and provides instead sweet and short .gpr files for each and every component as well as for eucrypt as a whole:

  • EuCrypt as a whole (top level, including ALL the different components), eucrypt/eucrypt.gpr:

     -- S.MG, 2018
    
    aggregate library project EuCrypt is
      for Project_Files use (
                              "mpi/mpi.gpr",
                              "smg_bit_keccak/smg_bit_keccak.gpr",
                              "smg_keccak/smg_keccak.gpr",
                              "smg_rsa/smg_rsa.gpr",
                              "smg_serpent/smg_serpent.gpr");
    
      for Library_Name use "EuCrypt";
      for Library_Kind use "static";
    
      for Library_Dir use "lib";
    end EuCrypt;
    
  • MPI component, eucrypt/mpi/mpi.gpr:

    -- S.MG, 2018
    
    project MPI is
      for Languages use ("C");
      for Library_Name use "MPI";
      for Library_Kind use "static";
    
      for Source_Dirs use (".", "include");
      for Object_Dir use "obj";
      for Library_Dir use "bin";
    
    end MPI;
    
  • MPI tests, eucrypt/mpi/tests/test_mpi.gpr

    -- S.MG, 2018
    
    with "../mpi.gpr";
    
    project test_MPI is
      for Languages use ("C");
    
      for Source_Dirs use (".");
      for Object_Dir use "obj";
      for Exec_Dir use ".";
    
      for Main use ("test_mpi.c");
    end test_MPI;
    
  • Bit-level keccak component, eucrypt/smg_bit_keccak/smg_bit_keccak.gpr:

     -- S.MG, 2018
    project SMG_Bit_Keccak is
      for Languages use ("Ada");
      for Library_Name use "SMG_Bit_Keccak";
      for Library_Kind use "static";
    
      for Source_Dirs use (".");
      for Object_Dir use "obj";
      for Library_Dir use "lib";
    
    end SMG_Bit_Keccak;
    
  • Bit-level Keccak tests, eucrypt/smg_bit_keccak/tests/smg_bit_keccak_test.gpr:

     -- Tests for SMG_Bit_Keccak (part of EuCrypt)
     -- S.MG, 2018
    
    with "../smg_bit_keccak.gpr";
    
    project SMG_Bit_Keccak_Test is
      for Source_Dirs use (".");
      for Object_Dir use "obj";
      for Exec_Dir use ".";
    
      for Main use ("smg_bit_keccak-test.adb");
    end SMG_Bit_Keccak_Test;
    
  • Keccak (word-level) component, eucrypt/smg_keccak/smg_keccak.gpr:

     -- S.MG, 2018
    project SMG_Keccak is
      for Languages use ("Ada");
      for Library_Name use "SMG_Keccak";
      for Library_Kind use "static";
    
      for Source_Dirs use (".");
      for Object_Dir use "obj";
      for Library_Dir use "lib";
    
    end SMG_Keccak;
    
  • Keccak (word-level) tests, eucrypt/smg_keccak/tests/smg_keccak-test.gpr:

     -- Tests for SMG_Keccak (part of EuCrypt)
     -- S.MG, 2018
    
    with "../smg_keccak.gpr";
    
    project SMG_Keccak_Test is
      for Source_Dirs use (".");
      for Object_Dir use "obj";
      for Exec_Dir use ".";
    
      for Main use ("smg_keccak-test.adb");
    end SMG_Keccak_Test;
    
  • RSA (including true random number generator and OAEP padding using Keccak as hash function) component, eucrypt/smg_rsa/smg_rsa.gpr:

     -- S.MG, 2018
    
    with "../mpi/mpi.gpr";
    with "../smg_keccak/smg_keccak.gpr";
    
    project SMG_RSA is
      for Languages use ("C");
      for Library_Name use "SMG_RSA";
      for Library_Kind use "static";
    
      for Source_Dirs use (".", "include");
      for Object_Dir use "obj";
      for Library_Dir use "bin";
    
    end SMG_RSA;
    
  • RSA tests, eucrypt/smg_rsa/tests/smg_rsa_tests.gpr:

     -- Tests for SMG_RSA (part of EuCrypt)
     -- S.MG, 2018
    
    with "../smg_rsa.gpr";
    
    project SMG_RSA_Tests is
      for Languages use("C");
      for Source_Dirs use (".");
      for Object_Dir use "obj";
      for Exec_Dir use ".";
    
      for Main use ("tests.c");
    end SMG_RSA_Tests;
    
  • Serpent component, eucrypt/smg_serpent/smg_serpent.gpr:

    -- S.MG, 2018
    
    project SMG_Serpent is
      for Languages use ("Ada");
      for Library_Name use "SMG_Serpent";
      for Library_Kind use "static";
    
      for Source_Dirs use ("src");
      for Object_Dir use "obj";
      for Library_Dir use "lib";
    
    end SMG_Serpent;
    
  • Serpent tests, eucrypt/smg_serpent/tests/smg_serpent_tests.gpr:

     -- Tests for SMG_Serpent (part of EuCrypt)
     -- S.MG, 2018
    
    with "../smg_serpent.gpr";
    
    project SMG_Serpent_Tests is
      for Source_Dirs use (".");
      for Object_Dir use "obj";
      for Exec_Dir use ".";
    
      for Main use ("testall.adb");
    end SMG_Serpent_Tests;
    

To compile anything, simply go to where the .gpr file of interest is and then run "gprbuild." As there is only one .gpr file per folder, you don't even need to specify *what* to build, as it's clever enough to figure out you mean that .gpr right there. Any components that are required by the one you are trying to build will be built automatically as part of the process. To clean up the result of any compilation, all you need to do is "gprclean -r". The r flag means recursive as otherwise gpr will not follow through to clean any dependencies it built.

With the "easy" part of building EuCrypt and its components out of the way, let's see the gnarly part of making a working wrapper for rsa and oaep, effectively mixing Ada code (the oaep part in smg_keccak) and C code (the rsa part in smg_rsa). Since I'm misfortunate enough to have Eulora's server code + underlying graphics engine in C, it follows that the rsa and oaep will need to be used from C anyway, so the approach here is to simply add to the smg_rsa component 2 new methods that do the OAEP padding + RSA encryption (and then in reverse for decrypting). The signatures for those 2 new methods can be found in eucrypt/smg_rsa/include/smg_rsa.h:


/*********
 * @param output - an MPI with KEY_LENGTH_OCTETS octets allocated space;
                   it will hold the result: (rsa(oaep(input), pk))
   @param input  - the plain-text message to be encrypted; maximum length is
                   245 octets (1960 bits)
   @param pk     - public key with which to encrypt
   NB: this method does NOT allocate memory for output!
   preconditions:
     - output IS different from input!
     - output has at least KEY_LENGTH_OCTETS octets allocated space
     - input is AT MOST max_len_msg octets long (ct defined in smg_oaep.ads)
 */
void rsa_oaep_encrypt( MPI output, MPI input, RSA_public_key *pk);

/*
 * Opposite operation to rsa_oaep_encrypt.
 * Attempts oaep_decrypt(rsa_decrypt(input))
 * @param output - an MPI to hold the result; allocated >= max_len_msg octets
 * @param input  - an MPI previously obtained with rsa_oaep_encrypt
 * @param sk     - the secret key with which to decrypt
 * @param success - this will be set to -1 if there is an error
 *
 * preconditions:
 *   - output IS different from input!
 *   - output has at least KEY_LENGTH_OCTETS octets allocated space
 *   - input is precisely KEY_LENGTH_OCTETS
 */
void rsa_oaep_decrypt( MPI output, MPI input, RSA_secret_key *sk, int *success);

The comments in the code snippet above should be clear enough: rsa_oaep_encrypt takes an MPI (the "message") and a public key as input, calls first oaep on the message and then rsa on the result, returning (via "output") the final result; rsa_oaep_decrypt does the reverse operations using the corresponding secret key and performing therefore rsa first, followed by oaep decrypt. Note that rsa_oaep_decrypt has an additional return value through the "success" flag - this is needed in order to signal to the caller potential clear failures at decryption (for instance if the input given is corrupted or otherwise a non-valid input). Obviously, the method works with a relatively limited definition of "success" since it can't really tell whether you got the original plain-text, nor is it meant to care about that: when the success flag is set to a negative value3 the caller can be absolutely sure that something went wrong; on the other hand, when the success flag is set to a positive value, the caller can only be assured that *something* was obtained through the decryption process from the original MPI. No assurance is given (nor can be given) regarding the value, integrity or meaning of that something.

Since the OAEP part is implemented in Ada, the same .h file as above also contains a few more declarations:

/*
 * This is the maximum length of a plain-text message (in octets) that can be
 * oeap+rsa encrypted in a single block. Its value is defined in smg_oaep.ads
 */
extern int max_len_msg;

/*
 * ada-exported oaep encrypt
 */
extern void oaep_encrypt_c( char* msg, int msglen,
                            char* entropy, int entlen,
                            char* encr, int encrlen,
                            int* success);

/*
 * ada-exported oaep decrypt
 */
extern void oaep_decrypt_c( char* encr, int encrlen,
                            char* decr, int* decrlen,
                            int* success);

Those oaep_encrypt_c and oaep_decrypt_c are Ada wrappers made specifically for use from C. The reason why such wrappers are needed (as opposed to using directly the Ada OAEP procedure for instance) is precisely the need to translate back and forth between C's char * unkempt "strings" and Ada's very tidy, trim and proper fixed-length Strings. The length of a char * from C can be anything and C doesn't bother apparently to pass it explicitly to Ada when calling an Ada function. At the same time, Ada is very careful to avoid writing out of bounds so its "Strings" are fixed-size arrays of char, with very well-known and unchangeable length4. To help with such interfacing problems, Ada conveniently offers the packages Interfaces.C and Interfaces.C.Strings. Using the later, one could work with chars_ptr that are in principle specifically made to handle the char * of C. However, since my purpose is not at all to import char * into C but rather to somehow bridge the chasm and recover the information from char * into a sane, fixed-length array of char, it follows that I'm using Interfaces.C.char_array. And to use those, C will have to pass explicitly the length of each char * parameter that it ever passes to Ada in any form. On its side, Ada will have to (unfortunately...) ...basically trust that value as correct and use therefore a fixed-length String of precisely that length, reading/writing at the address stored in the char* parameter at most length octets and not more.

On C side, the implementation of rsa_oaep_encrypt and rsa_oaep_decrypt are quite straightforward, making use of the previously discussed oaep_encrypt_c and oaep_decrypt_c external methods conveniently provided by Ada. From eucrypt/smg_rsa/rsa.c:

void rsa_oaep_encrypt( MPI output, MPI input, RSA_public_key *pk) {
  /* precondition: output is different from input */
  assert( output != input );

  /* precondition: output has enough memory allocated */
	unsigned int nlimbs_n = mpi_nlimb_hint_from_nbytes( KEY_LENGTH_OCTETS);
	assert( mpi_get_alloced( output ) >= nlimbs_n);

  /* precondition: input is at most max_len_msg octets long */
  unsigned int nlimbs_msg = mpi_nlimb_hint_from_nbytes( max_len_msg );
  assert( mpi_get_nlimbs( input ) <= nlimbs_msg);

  /* Step 1: oaep padding */
  /* get message char array and length */
  int msglen = 0;
  int sign;
  unsigned char * msg = mpi_get_buffer( input, &msglen, &sign);
  /* allocate memory for result */
  int encrlen = KEY_LENGTH_OCTETS;
  unsigned char * encr = xmalloc( encrlen );
  int entlen = KEY_LENGTH_OCTETS;
  unsigned char * entropy = xmalloc( entlen );
  int success = -10;
  /* call oaep until result is strictly < N of the rsa key to use */
  MPI oaep = mpi_alloc( nlimbs_n ); /* result of oaep encrypt/pad */

  int nread;
  do {
    /* get random bits */
		do {
      nread = get_random_octets( entlen, entropy );
		} while (nread != entlen);

    oaep_encrypt_c( msg, msglen, entropy, entlen, encr, encrlen, &success);
    if (success > 0) {
      /* set the obtained oaep to output mpi and compare to N of the rsa key */
      /* NB: 0-led encr WILL GET TRUNCATED!! */
      mpi_set_buffer( oaep, encr, encrlen, 0);
    }
    printf(".");
  }
  while ( success <=0 || mpi_cmp( oaep, pk->n ) >= 0 );

  printf("n");
  /* Step2 : call rsa for final result */
  public_rsa( output, oaep, pk );

  /* clear up */
  xfree( msg );
  xfree( encr );
  xfree( entropy );
  mpi_free( oaep );
}

void rsa_oaep_decrypt( MPI output, MPI input, RSA_secret_key *sk, int *success)
{
  *success = -1;
	unsigned int nlimbs_n = mpi_nlimb_hint_from_nbytes( KEY_LENGTH_OCTETS);
  unsigned int nlimbs_msg = mpi_nlimb_hint_from_nbytes( max_len_msg );

  /* preconditions */
  assert( output != input );
	assert( mpi_get_alloced( output ) >= nlimbs_msg);
  assert( mpi_get_nlimbs( input )  == nlimbs_n);

  /* rsa */
  MPI rsa_decr = mpi_alloc( nlimbs_n );
  secret_rsa( rsa_decr, input, sk );

  /* oaep */
  unsigned encr_len, decr_len;
  int sign, flag;
  char *oaep_encr = mpi_get_buffer( rsa_decr, &encr_len, &sign );
  char *oaep_decr = xmalloc( encr_len );
  decr_len = encr_len;
  oaep_decrypt_c( oaep_encr, encr_len, oaep_decr, &decr_len, &flag );

  /* check status */
  if ( flag > 0 ) {
    *success = 1;
    mpi_set_buffer( output, oaep_decr, decr_len, 0 );
  }
  else
    *success = -1;

  /* cleanup */
  mpi_free( rsa_decr );
  xfree( oaep_encr );
  xfree( oaep_decr );
}

The actual steps of both encryption and decryption above should be fairly obvious and further explained by the comments in the code. I'll just point to you one important decision taken in there: the oaep encryption is called in a loop until the result obtained is strictly smaller than the modulus (n) of the RSA key that will be used for encryption. This condition is a requirement of RSA and since oaep returns a block of the same length of TMSR RSA modulus, it follows that in some cases the oaep block will actually be a number bigger than the modulus, there is no way around this. Happily though, the TMSR RSA modulus has the highest bit set, meaning that an oaep block with highest bit 0 will surely be smaller than n. And since the highest bit of the resulting oaep block is quite random (TMSR OAEP uses *by design* a significant number of *true* random bits), it follows that it's really enough to simply check the result provided by oaep encryption, discard it if it's bigger than the modulus n and try again with another set of random bits - in most cases there should be at most 2 tries in order to get something that can be then safely fed to RSA encrypt.

To support the calling of Ada stuff from C as shown above, a lot of ugly code has to be added in Ada - the wrappers for the otherwise-perfectly-fine Ada oaep methods. Those are all at least contained in a single package, namely smg_oaep in eucrypt/smg_keccak/smg_oaep.ads and eucrypt/smg_keccak/smg_oaep.adb. First, methods to copy octet by octet from /to those char *:

  -- copy from Ada String to C char array and back, octet by octet

  -- This copies first Len characters from A to the first Len positions in S
  -- NB: this does NOT allocate /check memory!
  -- Caller has to ensure that:
  --    S has space for at least Len characters
  --    A has at least Len characters
  procedure Char_Array_To_String( A   : in Interfaces.C.char_array;
                                  Len : in Natural;
                                  S   : out String);

  -- This copies first Len characters from S to the first Len positions in A
  -- NB: there are NO checks or memory allocations here!
  -- Caller has to make sure that:
  --   S'Length >= Len
  --   A has allocated space for at least Len characters
  procedure String_To_Char_Array( S   : in String;
                                  Len : in Natural;
                                  A   : out Interfaces.C.char_array);

Then, the wrappers themselves and the pragma export for them:

 -- wrapper of oaep_encrypt for direct use from C
  -- NB: caller HAS TO provide the length of the Message (parameter LenMsg)
  -- NB: caller HAS TO provide the length of the Entropy (parameter LenEnt)
  -- NB: caller HAS TO provide the allocated space for result (LenEncr)
  -- NB: LenEncr HAS TO be at least OAEP_LENGTH_OCTETS!
  -- NB: LenEnt HAS TO be at least OAEP_LENGTH_OCTETS or this will FAIL!
  procedure OAEP_Encrypt_C( Msg       : in Interfaces.C.char_array;
                            MsgLen    : in Interfaces.C.size_t;
                            Entropy   : in Interfaces.C.char_array;
                            EntLen    : in Interfaces.C.size_t;
                            Encr      : out Interfaces.C.char_array;
                            EncrLen   : in Interfaces.C.size_t;
                            Success   : out Interfaces.C.Int);
  pragma Export( C, OAEP_Encrypt_C, "oaep_encrypt_c" );

 -- wrapper for use from C
  procedure oaep_decrypt_c( Encr    : in Interfaces.C.Char_Array;
                            EncrLen : in Interfaces.C.Int;
                            Decr    : out Interfaces.C.Char_Array;
                            DecrLen : in out Interfaces.C.Int;
                            Success : out Interfaces.C.Int);
  pragma Export( C, oaep_decrypt_c, "oaep_decrypt_c");

The implementations for all the above:

  -- This copies first Len characters from A to the first Len positions in S
  -- NB: this does NOT allocate /check memory!
  -- Caller has to ensure that:
  --    S has space for at least Len characters
  --    A has at least Len characters
  procedure Char_Array_To_String( A   : in Interfaces.C.char_array;
                                  Len : in Natural;
                                  S   : out String) is
  begin
    for Index in 0 .. Len - 1 loop
      S( S'First + Index ) := Character( A( Interfaces.C.size_t( Index )));
    end loop;
  end Char_Array_To_String;

  -- This copies first Len characters from S to the first Len positions in A
  -- NB: there are NO checks or memory allocations here!
  -- Caller has to make sure that:
  --   S'Length >= Len
  --   A has allocated space for at least Len characters
  procedure String_To_Char_Array( S   : in String;
                                  Len : in Natural;
                                  A   : out Interfaces.C.char_array) is
    C : Character;
  begin
    for Index in 0 .. Len - 1 loop
      C := S( S'First + Index );
      A( Interfaces.C.size_t( Index )) := Interfaces.C.Char( C );
    end loop;
  end String_To_Char_Array;

  procedure OAEP_Encrypt_C( Msg       : in Interfaces.C.char_array;
                            MsgLen    : in Interfaces.C.size_t;
                            Entropy   : in Interfaces.C.char_array;
                            EntLen    : in Interfaces.C.size_t;
                            Encr      : out Interfaces.C.char_array;
                            EncrLen   : in Interfaces.C.size_t;
                            Success   : out Interfaces.C.Int) is
    AdaMsgLen  : Natural := Natural( MsgLen );
    AdaEntLen  : Natural := Natural( EntLen );
    AdaEncrLen : Natural := Natural( EncrLen );
    AdaMsg     : String( 1 .. AdaMsgLen );
    AdaEntBlock: OAEP_Block;
    AdaResult  : OAEP_Block := ( others => '0' );
  begin
    Success := 0;
    -- check there is enough entropy and enoug output space, fail otherwise
    if AdaEntLen /= AdaEntBlock'Length or AdaEncrLen < AdaResult'Length then
      return;
    end if;
    -- translate to Ada
      --Interfaces.C.To_Ada( Msg, AdaMsg, AdaMsgLen );
    Char_Array_To_String( Msg, AdaMsgLen, AdaMsg );
      --Interfaces.C.To_Ada( Entropy, AdaEntropy, AdaEntLen );
    Char_Array_To_String( Entropy, AdaEntLen, AdaEntBlock );

    -- call the actual oaep encrypt
    OAEP_Encrypt( AdaMsg, AdaEntBlock, AdaResult );

    -- translate back to C, set success flag and return
       --Interfaces.C.To_C( AdaResult, CEncr, CEncrLen, False );
    -- EncrLen has already been tested to be at least AdaResult'Length
    String_To_Char_Array( AdaResult, AdaEncrLen, Encr );
    Success := 1;

  end OAEP_Encrypt_C;

  procedure oaep_decrypt_c( Encr    : in Interfaces.C.Char_Array;
                            EncrLen : in Interfaces.C.Int;
                            Decr    : out Interfaces.C.Char_Array;
                            DecrLen : in out Interfaces.C.Int;
                            Success : out Interfaces.C.Int) is
    AdaDecr    : OAEP_HALF := ( others => '0' );
    AdaEncr    : OAEP_Block:= ( others => '0' );
    AdaEncrLen : Natural := Natural( EncrLen );
    AdaDecrLen : Natural := 0;
    AdaFlag    : Boolean;
  begin
    -- check and set success flag/exit if needed
    Success := 0;
    if EncrLen /= OAEP_Block'Length then
      return;
    end if;

    -- translate to Ada: copy octet by octet as C.To_Ada is problematic
      -- Interfaces.C.To_Ada( Encr, AdaEncr, AdaEncrLen, False );
    Char_Array_To_String( Encr, AdaEncrLen, AdaEncr );

    -- actual decrypt
    OAEP_Decrypt( AdaEncr, AdaDecrLen, AdaDecr, AdaFlag );

    -- translate back to C
    AdaDecrLen := AdaDecrLen / 8;  -- from bits to octets
    if AdaFlag and
       Natural( DecrLen ) >= AdaDecrLen and
       AdaDecr'Length >= AdaDecrLen then
      Success := 1;
      DecrLen := Interfaces.C.Int( AdaDecrLen );
        -- Interfaces.C.To_C( AdaDecr, Decr, AdaDecrLen );
      String_To_Char_Array( AdaDecr, AdaDecrLen, Decr );
    end if;
  end oaep_decrypt_c;

In case you wonder why am I using this octet by octet copy thing instead of the procedures in Interfaces.C (To_Ada and To_C): I tried to use them and in some cases they still fail miserably, quite possibly because I don't yet fully understand them and therefore I'm not using them properly. So if you have experience with them for the sort of task you see here, please chime in. In any case, for as long as I can't trust them, I can't use them here, so octet by octet copying it is at least for now.

As seen before with the .gpr files, the compilation itself is quite straightforward. The call of Ada methods from C is also just a matter of "pragma export "foo" " on Ada side and "extern foo" on C side. The handy .gpr file takes otherwise care of the dependency introduced between the two EuCrypt components (since smg_rsa uses now smg_keccak) and we are all set. Time therefore to run the tests! More specifically, the *new* tests, in eucrypt/smg_rsa/tests/tests.c:

void test_oaep_encr_decr( int nruns ) {
  /* a set of RSA keys previously generated with eucrypt */
	RSA_public_key pk;
	pk.n = mpi_alloc(0);
	pk.e = mpi_alloc(0);

  RSA_secret_key sk;
  sk.n = mpi_alloc(0);
  sk.e = mpi_alloc(0);
  sk.d = mpi_alloc(0);
  sk.p = mpi_alloc(0);
  sk.q = mpi_alloc(0);
  sk.u = mpi_alloc(0);

  mpi_fromstr(sk.n, "0x
CD2C025323BEA46FFF2FA8D7A9D39817EA713421F4AE03FA8120641193892A70BFECF5
83101635A432110D3DDE6339E3CC7ECC0AD91C026FCACE832DD3888A6FCA7BCE56C390
5A5AC8C7BC921DA675E4B62489B254EB34659D547D71165BC998983A81937BD251AEE1
2D985EC387D5376F5DCC5EF7EC530FBD6FD2AA7285EE1AF3335EA73163F0954F30402E
D7B374EE84A97B1849B0674B0DA0A2050BD79B71ABB1559F3A9CFDB8557DED7BC90CF2
09E8A847E9C226140845B7D03842162E7DA5DD16326CB1F71A248D841FE9076A09911F
2F4F5E3EA44EA8DE40332BF00406990BCCF61C322A03C456EF3A98B341E0BDBC1088CE
683E78510E76B72C2BCC1EE9AEDD80FFF18ABFC5923B2F36B581C25114AB2DF9F6C2B1
9481703FD19E313DCD7ACE15FA11B27D25BCE5388C180A7E21167FB87750599E1ED7C7
50F4A844E1DC2270C62D19671CF8F4C25B81E366B09FC850AE642136D204A9160AEECE
575B57378AA439E9DD46DC990288CD54BAA35EEE1C02456CD39458A6F1CBF012DCEDF4
27CCF3F3F53645658FC49C9C9D7F2856DB571D92B967AB5845514E0054DDB49099F5DD
04A6F6F5C5CE642276834B932881AEB648D1F25E9223971F56E249EF40CF7D80F22621
CDD0260E9E7D23746960ADB52CF2987584FB1DE95A69A39E5CB12B76E0F5C1A0529C0C
065D2E35720810F7C7983180B9A9EA0E00C11B79DC3D");

  mpi_fromstr(sk.e, "0x
DD4856B4EE3D099A8604AE392D8EFEC094CDF01546A28BE87CB484F999E8E75CDFCD01
D04D455A6A9254C60BD28C0B03611FC3E751CC27EF768C0B401C4FD2B27C092834A6F2
49A145C4EDC47A3B3D363EC352462C945334D160AF9AA72202862912493AC6190AA3A6
149D4D8B9996BA7927D3D0D2AD00D30FD630CF464E6CAF9CF49355B9A70E05DB7AE915
F9F602772F8D11E5FCDFC7709210F248052615967090CC1F43D410C83724AA5912B2F0
52E6B39449A89A97C79C92DC8CB8DEEFCF248C1E1D2FC5BFE85165ECA31839CAA9CEB3
3A92EBDC0EB3BAC0F810938BB173C7DA21DCBB2220D44CBA0FD40A2C868FC93AC5243E
C137C27B0A76D65634EBB3");

  mpi_fromstr(sk.d, "0x
7C8A6FA1199D99DCA45E9BDF567CA49D02B237340D7E999150BC4883AE29DEC5158521
B338F35DC883792356BDDBB3C8B3030A6DD4C6522599A3254E751F9BA1CB1061C5633C
81BBFACF6FCD64502614102DFED3F3FA284066C342D5E00953B415915331E30812E5FB
CD6680ADCCDEE40B8376A3A225F2E160EA59C7566804526D73BB660A648A3EF9802313
B2F841E8458B2AAACE7AACF31083E8F3F630298138393BC88BBD7D4AA4334949651D25
365B10DBF4A4A08E20A6CC74BFDD37C1C38E2ADC2A283DF06590DF06B46F67F6ACA67F
AC464C795261659A2F9558802D0BBAA05FD1E1AF2CDC70654723DF7EFAEA148B8CDBEB
C89EA2320AB9BBB1BC4311475DF3D91446F02EF192368DFEBAC598CCFD4407DEC58FDC
1A94CCDD6E5FBA9C52164ACEA8AEE633E557BCCEACB7A1AF656C379482D784A120A725
32F9B2B35173D505F21D5AD4CB9511BC836DC923730B70291B70290A216CA3B21CFF79
E895C35F4F7AF80E1BD9ED2773BD26919A76E4298D169160593E0335BE2A2A2D2E8516
948F657E1B1260E18808A9D463C108535FB60B3B28F711C81E5DE24F40214134A53CE5
9A952C8970A1D771EBEFFA2F4359DCF157995B3F1950DE3C6EC41B7FF837148F55F323
372AF3F20CE8B8038E750C23D8F5041FA951327859B0E47483F0A47103EF808C72C251
006FA526245291C8C84C12D2EF63FB2301EA3EEDA42B");

  mpi_fromstr(sk.p, "0x
E236732452039C14EC1D3B8095BDDCFB7625CE27B1EA5394CF4ED09D3CEECAA4FC0BF6
2F7CE975E0C8929CE84B0259D773EA038396479BF15DA065BA70E549B248D77B4B23ED
A267308510DBEE2FD44E35D880EE7CFB81E0646AA8630165BD8988C3A8776D9E704C20
AA25CA0A3C32F27F592D5FD363B04DD57D8C61FFDCDFCCC59E2913DE0EE47769180340
E1EA5A803AA2301A010FF553A380F002601F0853FCACDB82D76FE2FACBCD6E5F294439
0799EA5AE9D7880D4E1D4AE146DC1D4E8495B9DD30E57E883923C5FC26682B7142D35C
D8A0FC561FE725A6CF419B15341F40FE0C31132CBD81DD8E50697BD1EBFFA16B522E16
F5B49A03B707218C7DA60B");

  mpi_fromstr(sk.q, "0x
E830482A3C4F5C3A7E59C10FF8BA760DB1C6D55880B796FFDA4A82E0B60E974E81D04B
2A4AD417823EBFB4E8EFB13782943562B19B6C4A680E3BA0C8E37B5023470F4F1AC1F8
A0B10672EF75CD58BCD45E6B14503B8A6A70AFE79F6201AF56E7364A1C742BE1453FD2
24FDC9D66522EAF4466A084BCB9E46D455A2946E94CBF028770F38D0B741C2CC59308F
71D8C2B4B9C928E0AE8D68DEB48A3E9EFD84A10301EBD55F8221CA32FC567B306B2A8E
116350AFB995859FDF4378C5CFD06901494E8CFA5D8FAC564D6531FA8A2E4761F5EFBA
F78750B6F4662BE9EA4C2FAD67AF73EEB36B41FC15CB678810C19A51DF23555695C4C1
546F3FACA39CAA7BB8DBD7");

  mpi_fromstr(sk.u, "0x
846232322775C1CD7D5569DC59E2F3E61A885AE2E9C4A4F8CB3ACBE8C3A5441E5FE348
A2A8AC9C2998FBF282222BF508AA1ECF66A76AEDD2D9C97028BFD3F6CA0542E38A5312
603C70B95650CE73F80FDD729988FBDB5595A5BF8A007EA34E54994A697906CE56354C
E00DF10EB711DEC274A62494E3D350D88736CF67A477FB600AC9F1D6580727585092BF
5EBC092CC4D6CF75769051033A1197103BE269942F372168A53771746FBA18ED6972D5
0B935A9B1D6B5B3DD50CD89A27FE93C10924E9103FACF7B4C5724A046C3D3B50CC1C78
5F5C8E00DBE1D6561F120F5294C170914BC10F978ED4356EED67A9F3A60D70AFE540FC
5373CBAE3D0A7FD1C87273");

  /* copy the public key components */
  pk.n = mpi_copy( sk.n );
  pk.e = mpi_copy( sk.e );

  /* some plain text message */
	MPI msg = mpi_alloc(0);
	mpi_fromstr(msg, "0x
5B6A8A0ACF4F4DB3F82EAC2D20255E4DF3E4B7C799603210766F26EF87C8980E737579
EC08E6505A51D19654C26D806BAF1B62F9C032E0B13D02AF99F7313BFCFD68DA46836E
CA529D7360948550F982C6476C054A97FD01635AB44BFBDBE2A90BE06F7984AC8534C3
28097EF92F6E78CAE0CB97");

  /* actual testing */
	printf("TEST verify oaep_encr_decr on message: n");
	mpi_print( stdout, msg, 1);
	printf("n");

  int nlimbs_n = mpi_nlimb_hint_from_nbytes( KEY_LENGTH_OCTETS);
	MPI encr = mpi_alloc( nlimbs_n );
	MPI decr = mpi_alloc( nlimbs_n );
  int success;

  adainit();
  rsa_oaep_encrypt( encr, msg, &pk );
  rsa_oaep_decrypt( decr, encr, &sk, &success );

  if (success <= 0 ||
      mpi_cmp(encr, msg) == 0 ||
      mpi_cmp(msg, decr) != 0)
    printf("FAILED: success flag is %dn", success);
  else
    printf("PASSEDn");

  /* attempt to decrypt corrupted block */
  mpi_clear( decr );
  rsa_oaep_decrypt( decr, pk.n, &sk, &success);
  if (success > 0)
    printf("FAILED: attempt to decrypt non-/corrupted oaep blockn");
  else
    printf("PASSED: attempt to decrypt non-/corrupted oaep blockn");
  adafinal();

  /* clean up */
  mpi_free( sk.n );
  mpi_free( sk.e );
  mpi_free( sk.d );
  mpi_free( sk.p );
  mpi_free( sk.q );
  mpi_free( sk.u );

	mpi_free( pk.n );
	mpi_free( pk.e );

	mpi_free( msg );
	mpi_free( encr );
	mpi_free( decr );
}

void test_mpi_buffer() {
  unsigned int noctets = 10;
  int nlimbs = mpi_nlimb_hint_from_nbytes( noctets );
  MPI m = mpi_alloc( nlimbs );
  unsigned char *setbuffer = xmalloc( noctets );
  unsigned char *getbuffer;
  unsigned int i, sign, mpilen, nerrors;

  for (i=0; i< noctets; i++)
    setbuffer[i] = i;

  mpi_set_buffer( m, setbuffer, noctets, 0);

  getbuffer = mpi_get_buffer( m, &mpilen, &sign );

  if (mpilen == noctets -1 ) {
    nerrors = 0;
    for (i=0;i0)
      printf("FAIL: got %d different values!n", nerrors);
    else printf("PASSED: mpi_get/set_buffern");
  }

  mpi_free(m);
  xfree(setbuffer);
  xfree(getbuffer);
}

The test_oaep_encr_decr method uses a pair of TMSR RSA keys (previously generated by smg_rsa) to attempt oaep+rsa on a message, using rsa_oaep_encrypt and rsa_oaep_decrypt. There is also an attempt at decrypting a "corrupted" oaep block and this correctly fails with the success flag set accordingly.

The second test method in there is ...bonus. It follows a further discovery of the unexpected in the mpi "code": the mpi_set_buffer and mpi_get_buffer methods for an mpi are not exactly symmetrical. In other words, calling mpi_get_buffer for the same mpi on which you previously (immediately before!) called mpi_set_buffer with a given buffer b will NOT always return EXACTLY same b! That's because mpi_set_buffer will helpfully trim leading-0 octets from the buffer you pass, so if you pass number 009, it will store 9 only and therefore it will return...9. To reflect this, the test gives a warning as result - basically I'm flagging this for the future, not changing anything at the moment. This MPI implementation has already eaten an incredible amount of time with very little to show for it in return and I foresee that it will still eat even more time with similarly poor returns on it. Moreover, it has already gotten to the stage where I think it would have been probably better *not* to have an mpi implementation at all than to have this one. I can only add that I would certainly throw it away and implement a useful Ada library, if not for the fact that there really, really are many more pressing things to do right now. Sigh.

Getting back to happier facts, this chapter quite completes EuCrypt as the library contains now everything that it is currently meant to contain. Since the original Introduction, there has been a change in that the smg_comm component was taken out of EuCrypt: the reason for this removal is that smg_comm is very specific to Eulora and as such it is naturally a user of EuCrypt rather than part of it; by contrast, EuCrypt offers generic crypto routines (despite being made because Eulora needs it). The .vpatch and its signature can be found on my Reference Code Shelf and are also linked directly here for your convenience:

Any further chapters -if and when they might be- will deal with cosmetic changes or fixing of errors if any are found. At the moment there aren't any further components /parts planned as part of EuCrypt itself. Give it a spin!


  1. I'm currently using Adacore's 2016 version with gcc 4.9.4 as previously mentioned in the logs

  2. Note that here as everywhere I really recommend the Adacore version as opposed to whatever your favourite linux distribution can find: the reason for this recommendation is pure and painful experience - while I think that one *can* get a working setup with non-Adacore GNAT and/or GPR and random-flavour gcc, it's certainly not a straightforward task and there are so many things that can (and do!) go wrong (mismatching versions of various tools is the one I stumbled on repeatedly) that it's not worth it, simply. So do yourself a favour and get the only ada-tron that actually... works - Adacore's. 

  3. Grrr, why can't this be a Boolean as it should be!!! 

  4. Yes, I know you can use unbounded strings instead but I won't be using those unless I really, really have no choice. I suggest you do the same rather than writing "in Ada" while importing as much C-uncertainty as possible.