The HMAC Ada Library Experiment

I was relieved to learn more about HMAC, from IETF RFC 2104.  It's a dependency for a program I plan
to write and I had been concerned with how well it would fit in with my SHA library.  Its simple and
generic nature meant a generic unit would be that most appropriate choice, however, which suited me.

Writing this has revealed to me many small issues that should influence the design of my SHA library
and a future HMAC library.  Particularly, the indices of the unit block types should be given names,
as distasteful as I find that to be, to spare the user of such a generic unit from needing to define
them; and the SHA library should also provide a variant of that full Hash subprogram which accepts a
status value other than the initial status, as it would ease an implementation of HMAC and the like.

It's now clear to me a generic HMAC should be a package rather than subprogram.  I wanted to avoid a
larger and more complex interface, but an implementation of HMAC can make optimizations, the lack of
which disqualify it for serious use; in particular, the processed keys are exactly one unit block in
length, which means a real implementation of HMAC can hash them once and later reuse them, and which
plays with that aforementioned subprogram variant which would avoid any copying of the data to hash.

Flaws with the current toy implementation are mostly its lack of those aforementioned optimizations.
In particular, the lack of that full Hash subprogram variant means the concatenation operator likely
causes the data parameter to be copied, which is avoidable in a better implementation.  Fortunately,
that SHA library design already provides a Hash subprogram for individual blocks, so a comprehensive
HMAC library can largely avoid the redundancy.  He who wanted to process the data piecewise so could
begin with the HMAC library, continue with the SHA library, and finish with a subprogram in the HMAC
library that would accept the outer key pad and the digest from the final inner SHA subprogram call.

Still, I'm pleased with some aspects of my design.  Making the masks parameters is needed to support
different block units, and they're not of Unit_Type because a Unit_Type of bit can't be used in that
case or with any other units smaller than the repeating mask pattern.  That ease with which an array
can be specified in Ada makes this a very minor issue, however, as this example instantiation shows:

hmac_test.adb:
with Ada.Text_IO;
with SHA256, HMAC; use SHA256;

procedure HMAC_Test is
   subtype Octet_Block_Range is Positive range 1 .. 64;
   function HMAC_SHA256 is new HMAC
     (Octet, Positive, Octet_Block_Range, Octet_Array, Octet_Block, Digest,
      (others => 16#36#), (others => 16#5C#), To_Octets);
begin -- The following is one test case from IETF RFC 4231, in particular the ``4.4.  Test Case 3''.
   Ada.Text_IO.Put_Line(To_String(HMAC_SHA256(Key => (1 .. 20 => 16#AA#, others => 0),
                                              Data => (1 .. 50 => 221))));
end HMAC_Test;


hmac.ads:
-- HMAC - Provide a single generic HMAC function which should suffice for plenty of reasonable uses.
-- Copyright (C) 2023 Prince Trippy <programmer@verisimilitudes.net>.

-- This program is free software: you can redistribute it and/or modify it under the terms of the
-- GNU Affero General Public License version 3 as published by the Free Software Foundation.

-- This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
-- even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-- See the GNU Affero General Public License for more details.

-- You should have received a copy of the GNU Affero General Public License along with this program.
-- If not, see <http://www.gnu.org/licenses/>.

generic -- I'm not entirely pleased with these names, or the tighter index specifications.  Oh well.
   type   Unit_Type is mod <>;
   type  Index_Type is range <>;
   type Block_Range is range <>;
   type  Unit_Array is array (Index_Type range <>) of Unit_Type;
   type  Unit_Block is array (Block_Range) of Unit_Type;
   type Digest_Type is private;
   Inner_Mask, Outer_Mask : in Unit_Block;
   with function To_Unit (Datum : in Digest_Type) return Unit_Array;
   with function Hash (Data : in Unit_Array) return Digest_Type is <>;
function HMAC (Key : in Unit_Block; Data : in Unit_Array) return Digest_Type;


hmac.adb:
-- HMAC - Provide a single generic HMAC function which should suffice for plenty of reasonable uses.
-- Copyright (C) 2023 Prince Trippy <programmer@verisimilitudes.net>.

-- This program is free software: you can redistribute it and/or modify it under the terms of the
-- GNU Affero General Public License version 3 as published by the Free Software Foundation.

-- This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
-- even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-- See the GNU Affero General Public License for more details.

-- You should have received a copy of the GNU Affero General Public License along with this program.
-- If not, see <http://www.gnu.org/licenses/>.

function HMAC (Key : in Unit_Block; Data : in Unit_Array) return Digest_Type is
   function "xor" (Left, Right : in Unit_Block) return Unit_Block is
      Result : Unit_Block;
   begin
      for I in Unit_Block'Range loop
         Result(I) := Left(I) xor Right(I);
      end loop;
      return Result;
   end "xor";
begin
   return Hash(Unit_Array(Outer_Mask xor Key) &
                 To_Unit(Hash(Unit_Array(Inner_Mask xor Key) & Data)));
end HMAC;