~ 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