SMG Comms Chapter 8: Keys Management



November 17th, 2018 by Diana Coman

~ This is a work in progress towards an Ada implementation of Eulora's communication protocol. Start with Chapter 1.~

This chapter makes a few changes to previous code and adds a new part to handle read/write of messages concerning keys management (4.2 and 5.3 in the current protocol specification):

  • (Changed): I've refactored the code for read/write of messages transporting Serpent keys (i.e. the code from Chapter 7 of the SMG Comms series). At the time I wrote those for Chapter 7 I wasn't even sure that I'll continue with that approach to serializing the data structure so I did not bother too much/too early with other considerents. As it turned out that the approach implemented was indeed the most appropriate for the task at hand, the next focus was on making the best use of it given that both Serpent and RSA messages that transport keys effectively have the same structure and differ only in their ID (first octet) and length of padding (due to different sizes). So there clearly was no point in repeating the actual serialization code in 2 separate places, one for Serpent messages and one for RSA messages. Instead, the common core is now done by methods that are private to the Messages package and are called with the correct parameters by the read/write methods for Serpent and RSA messages. Having previously defined RSA and Serpent message types to reflect precisely what they are, namely subtypes of the generic, raw, "Octets" type helped significantly, of course: the private read/write simply take/provide Octets and ID, focusing on the common core and ignoring the rest (which is handled by the caller since that's the point at which Serpent/RSA is a concern). As a result, the public methods are now very short since the actual work is done in the private methods:
    package body Messages is
    
      ----------------------
      -- Serpent Messages --
      ----------------------
    
      procedure Write_SKeys_SMsg( Keyset  : in Serpent_Keyset;
                                  Counter : in Interfaces.Unsigned_16;
                                  Msg     : out Raw_Types.Serpent_Msg) is
      begin
        -- call internal write on Octets with correct type id
        Write_SKeys( Keyset, Counter, SKeys_S_Type, Msg );
      end Write_SKeys_SMsg;
    
      -- Reads a Serpent keyset from given Serpent Message
      procedure Read_SKeys_SMsg( Msg     : in Raw_Types.Serpent_Msg;
                                 Counter : out Interfaces.Unsigned_16;
                                 Keyset  : out Serpent_Keyset) is
      begin
        -- check type id and call internal Read_SKeys if correct
        if Msg(Msg'First) /= SKeys_S_Type then
          raise Invalid_Msg;
        else
          Read_SKeys( Msg, Counter, Keyset );
        end if;
      end Read_SKeys_SMsg;
    
      -- writes given key mgm structure into a Serpent message
      procedure Write_KMgm_SMsg( KMgm    : in Keys_Mgm;
                                 Counter : in Interfaces.Unsigned_16;
                                 Msg     : out Raw_Types.Serpent_Msg) is
      begin
        -- call internal write of key mgm with correct type ID
        Write_KMgm( KMgm, Counter, Key_Mgm_S_Type, Msg );
      end Write_KMgm_SMsg;
    
      -- reads a key mgm structure from the given Serpent message
      procedure Read_KMgm_SMsg( Msg     : in Raw_Types.Serpent_Msg;
                                Counter : out Interfaces.Unsigned_16;
                                KMgm    : out Keys_Mgm) is
      begin
        -- check type id and call internal Read_KMgm if correct
        if Msg(Msg'First) /= Key_Mgm_S_Type then
          raise Invalid_Msg;
        else
          Read_KMgm( Msg, Counter, KMgm );
        end if;
      end Read_KMgm_SMsg;
    
      ------------------
      -- RSA Messages --
      ------------------
    
      procedure Write_SKeys_RMsg( Keyset  : in Serpent_Keyset;
                                  Counter : in Interfaces.Unsigned_16;
                                  Msg     : out Raw_Types.RSA_Msg) is
      begin
        -- call internal write of Serpent keys with correct type ID
        Write_SKeys( Keyset, Counter, SKeys_R_Type, Msg );
      end Write_SKeys_RMsg;
    
      procedure Read_SKeys_RMsg( Msg     : in Raw_Types.RSA_Msg;
                                 Counter : out Interfaces.Unsigned_16;
                                 Keyset  : out Serpent_Keyset) is
      begin
        -- check type id and call internal Read_SKeys if correct
        if Msg(Msg'First) /= SKeys_R_Type then
          raise Invalid_Msg;
        else
          Read_SKeys( Msg, Counter, Keyset );
        end if;
      end Read_SKeys_RMsg;
    
      procedure Write_KMgm_RMsg( KMgm    : in Keys_Mgm;
                                 Counter : in Interfaces.Unsigned_16;
                                 Msg     : out Raw_Types.RSA_Msg) is
      begin
        -- call internal write of key mgm with correct type ID
        Write_KMgm( KMgm, Counter, Key_Mgm_R_Type, Msg );
      end Write_KMgm_RMsg;
    
      procedure Read_KMgm_RMsg( Msg     : in Raw_Types.RSA_Msg;
                                Counter : out Interfaces.Unsigned_16;
                                KMgm    : out Keys_Mgm) is
      begin
        -- check type id and call internal Read_KMgm if correct
        if Msg(Msg'First) /= Key_Mgm_R_Type then
          raise Invalid_Msg;
        else
          Read_KMgm( Msg, Counter, KMgm );
        end if;
      end Read_KMgm_RMsg;
    
      ------------------
      -- private part --
      ------------------
      procedure Cast_LE( LE: in out Raw_Types.Octets ) is
      begin
        -- flip octets ONLY if native is big endian.
        if System.Default_Bit_Order = System.High_Order_First then
          declare
            BE: constant Raw_Types.Octets := LE;
          begin
            for I in 1..LE'Length loop
              LE(LE'First+I-1) := BE(BE'Last-I+1);
            end loop;
          end;
        end if;
        -- NOTHING to do for native little endian
      end Cast_LE;
    
      procedure Write_SKeys( Keyset  : in Serpent_Keyset;
                             Counter : in Interfaces.Unsigned_16;
                             Type_ID : in Interfaces.Unsigned_8;
                             Msg     : out Raw_Types.Octets) is
        Pos   : Integer := Msg'First;
        Check : CRC32.CRC32;
        PadLen: Integer;
        K     : Serpent.Key;
      begin
        -- write Type ID
        Msg(Pos) := Type_ID;
        Pos := Pos + 1;
    
        -- write count of keys (NB: this IS 8 bits by definition)
        Msg(Pos) := Keyset.Keys'Length;
        Pos := Pos + 1;
    
        -- write keys
        for I in Keyset.Keys'Range loop
          -- retrieve Key to write
          K := Keyset.Keys( I );
    
          -- write key itself
          Msg(Pos..Pos+K'Length-1) := K;
          -- ensure little endian order in message
          Cast_LE(Msg(Pos..Pos+K'Length-1));
          Pos := Pos + K'Length;
    
          -- write CRC of key
          Check := CRC32.CRC( K );
          Msg(Pos..Pos+3) := Raw_Types.Cast(Check);
          Cast_LE(Msg(Pos..Pos+3));
          Pos := Pos + 4;
        end loop;
    
        -- write flag
        Msg(Pos) := Keyset.Flag;
        Pos := Pos + 1;
    
        -- write message counter
        Msg(Pos..Pos+1) := Raw_Types.Cast(Counter);
        Cast_LE(Msg(Pos..Pos+1));
        Pos := Pos + 2;
    
        -- write padding as needed; endianness is irrelevant here
        PadLen := Msg'Last - Pos + 1;
        if PadLen > 0 then
          declare
            Pad : Raw_Types.Octets(1..PadLen);
          begin
            RNG.Get_Octets( Pad );
            Msg(Pos..Pos+PadLen-1) := Pad;
          end;
        end if;
    
      end Write_SKeys;
    
      procedure Read_SKeys( Msg     : in Raw_Types.Octets;
                            Counter : out Interfaces.Unsigned_16;
                            Keyset  : out Serpent_Keyset) is
        Pos: Integer := Msg'First;
      begin
        -- read type and check
        if Msg(Pos) = SKeys_S_Type or
           Msg(Pos) = SKeys_R_Type then
          Pos := Pos + 1;
        else
          raise Invalid_Msg;
        end if;
    
        -- read count of keys and check
        if Msg(Pos) in Keys_Count'Range then
          declare
            N     : Keys_Count := Keys_Count(Msg(Pos));
            KS    : Serpent_Keyset(N);
            K     : Serpent.Key;
            Check : CRC32.CRC32;
            O4    : Raw_Types.Octets_4;
            O2    : Raw_Types.Octets_2;
          begin
            Pos := Pos + 1;
            --read keys and check crc for each
            for I in 1 .. N loop
              -- read key and advance pos
              K := Msg(Pos..Pos+K'Length-1);
              Cast_LE(K);
              Pos := Pos + K'Length;
              -- read crc and compare to crc32(key)
              O4 := Msg(Pos..Pos+3);
              Cast_LE(O4);
              Check   := Raw_Types.Cast(O4);
              Pos := Pos + 4;
              if Check /= CRC32.CRC(K) then
                raise Invalid_Msg;
              end if;
              -- if it got here, key is fine so add to set
              KS.Keys(KS.Keys'First + I -1) := K;
            end loop;
            -- read and set flag
            KS.Flag := Msg(Pos);
            Pos := Pos + 1;
            -- read and set message counter
            O2 := Msg(Pos..Pos+1);
            Cast_LE(O2);
            Counter := Raw_Types.Cast(O2);
            -- rest of message is padding so it's ignored
            -- copy keyset to output variable
            Keyset := KS;
          end;
        else
          raise Invalid_Msg;
        end if;
      end Read_SKeys;
    
      -- writes given key management structure to the given octets array
      procedure Write_KMgm( KMgm    : in Keys_Mgm;
                            Counter : in Interfaces.Unsigned_16;
                            Type_ID : in Interfaces.Unsigned_8;
                            Msg     : out Raw_Types.Octets) is
        Pos   : Integer := Msg'First;
      begin
        -- write given type id
        Msg(Pos) := Type_ID;
        Pos := Pos + 1;
    
        -- write count of server keys requested
        Msg(Pos) := KMgm.N_Server;
        Pos := Pos + 1;
    
        -- write count of client keys requested
        Msg(Pos) := KMgm.N_Client;
        Pos := Pos + 1;
    
        -- write id of key preferred for further inbound Serpent messages
        Msg(Pos) := KMgm.Key_ID;
        Pos := Pos + 1;
    
        -- write count of burnt keys in this message
        Msg(Pos..Pos) := Cast( KMgm.N_Burnt );
        Pos := Pos + 1;
    
        -- if there are any burnt keys, write their ids
        if KMgm.N_Burnt > 0 then
          Msg( Pos .. Pos + KMgm.Burnt'Length - 1 ) := KMgm.Burnt;
          Pos := Pos + KMgm.Burnt'Length;
        end if;
    
        -- write the message count
        Msg(Pos..Pos+1) := Raw_Types.Cast( Counter );
        Cast_LE( Msg(Pos..Pos+1) );
        Pos := Pos + 2;
    
        -- pad with random octets until the end of Msg
        RNG.Get_Octets( Msg(Pos..Msg'Last) );
    
      end Write_KMgm;
    
      -- attempts to read from the given array of octets a key management structure
      procedure Read_KMgm( Msg     : in Raw_Types.Octets;
                           Counter : out Interfaces.Unsigned_16;
                           KMgm    : out Keys_Mgm) is
        Pos       : Integer := Msg'First;
        Burnt_Pos : Integer := Msg'First + 4;
      begin
        -- read type and check
        if Msg(Pos) = Key_Mgm_S_Type or
           Msg(Pos) = Key_Mgm_R_Type then
          Pos := Pos + 1;
        else
          raise Invalid_Msg;
        end if;
    
        -- read the count of burnt keys and check
        -- NB: Burnt_Pos IS in range of Counter_8bits since it's an octet
        declare
          N_Burnt : Counter_8bits := Counter_8bits(Msg(Burnt_Pos));
          Mgm     : Keys_Mgm(N_Burnt);
          O2      : Raw_Types.Octets_2;
        begin
          -- read count of server keys requested
          Mgm.N_Server := Msg(Pos);
          Pos := Pos + 1;
    
          -- read count of client keys requested
          Mgm.N_Client := Msg(Pos);
          Pos := Pos + 1;
    
          -- read ID of Serpent key preferred for further inbound messages
          Mgm.Key_ID   := Msg(Pos);
          Pos := Pos + 2; --skip the count of burnt keys as it's read already
    
          -- read ids of burnt keys, if any
          if N_Burnt > 0 then
            Mgm.Burnt := Msg(Pos..Pos+N_Burnt-1);
            Pos := Pos + N_Burnt;
          end if;
    
          -- read and set message counter
          O2 := Msg(Pos..Pos+1);
          Cast_LE(O2);
          Counter := Raw_Types.Cast(O2);
          -- rest of message is padding so it's ignored
          -- copy the keys mgm structure to output param
          KMgm := Mgm;
        end;
      end Read_KMgm;
    
    end Messages;
    
  • (Changed): The Keys_Mgm record in Data_Structs.ads:
    • (Changed): The array of burnt keys was initially declared as holding the actual keys rather than just the ids. I've corrected this so that now the "Burnt" part of Keys_Mgm is simply an array of Unsigned_8 (i.e. Octets type)
    • (Changed): The parameter N_Burnt (the number of IDs of burnt keys in the structure) was initially declared as Unsigned_8. However, after changing the array to the correct Octets type, there was a bit of type-trouble: on one hand, the Octets type can have any length, so the type of its range can't be limited to Unsigned_8 and Unsigned_8 is NOT a subtype of Integer (it's a modular type instead); on the other hand, N_Burnt has to appear by itself in the definition of the record type since it is a discriminant so it couldn't be cast to an Integer subtype that Octets would accept. To address this, I defined the type Counter_8bit to match precisely the meaning of N_Burnt (or any other similar counter): values between 0 and 255.
    • (Changed): Since the N_Burns counter can be 0, it follows that the array of burnt ids should *not* always be present in the record (the only way to have an array of length 0 is effectively to not have it at all). Consequently, I further changed the Keys_Mgm record declaration to reflect this:
        ------------------------------
        -- Serpent Keys Management
        subtype Counter_8bits is Natural range 0..255;
        function Cast is new Ada.Unchecked_Conversion( Counter_8bits,
                                                       Raw_Types.Octets_1 );
        function Cast is new Ada.Unchecked_Conversion( Raw_Types.Octets_1,
                                                       Counter_8bits );
        type Keys_Mgm (N_Burnt: Counter_8bits := 0) is
          record
            -- count of server keys requested
            N_Server: Interfaces.Unsigned_8;
            -- count of client keys requested
            N_Client: Interfaces.Unsigned_8;
            -- ID of Serpent key preferred for further inbound Serpent msgs.
            Key_ID  : Interfaces.Unsigned_8;
            -- IDs of Serpent keys burnt by this message
            case N_Burnt is
              when 0 =>
                null;
              when others =>
                Burnt   : Raw_Types.Octets( 1..N_Burnt );
            end case;
          end record;
      
  • (Added): I've used the Keys_Mgm record from above and the same approach as for keys messages (common core serialization in private methods that are called by public read/write with correct IDs and Octets parameters) to implement the read/write from/to messages for keys management, whether Serpent or RSA messages.
  • (Added): New tests for the new read/write methods for key management messages.
  • (Changed): The existing tests for read/write of messages transporting keys are now updated to test also the read/write to/from RSA messages of the relevant type.

The .vpatch and my signature for it is on the Reference Code Shelf as well as linked here for your convenience:

Comments feed: RSS 2.0

Leave a Reply