]> git.proxmox.com Git - mirror_edk2.git/blobdiff - NetworkPkg/IScsiDxe/IScsiCHAP.c
NetworkPkg/IScsiDxe: support multiple hash algorithms for CHAP
[mirror_edk2.git] / NetworkPkg / IScsiDxe / IScsiCHAP.c
index 4cfbba7b5cdba32d9774876413f6a0e9e62aff8f..351bf329b73996bec9fa101317da8624d82239fa 100644 (file)
@@ -1,48 +1,84 @@
 /** @file\r
-  This file is for Challenge-Handshake Authentication Protocol (CHAP) Configuration.\r
+  This file is for Challenge-Handshake Authentication Protocol (CHAP)\r
+  Configuration.\r
 \r
-Copyright (c) 2004 - 2011, Intel Corporation. All rights reserved.<BR>\r
-This program and the accompanying materials\r
-are licensed and made available under the terms and conditions of the BSD License\r
-which accompanies this distribution.  The full text of the license may be found at\r
-http://opensource.org/licenses/bsd-license.php\r
-\r
-THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,\r
-WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
+Copyright (c) 2004 - 2018, Intel Corporation. All rights reserved.<BR>\r
+SPDX-License-Identifier: BSD-2-Clause-Patent\r
 \r
 **/\r
 \r
 #include "IScsiImpl.h"\r
 \r
+//\r
+// Supported CHAP hash algorithms, mapped to sets of BaseCryptLib APIs and\r
+// macros. CHAP_HASH structures at lower subscripts in the array are preferred\r
+// by the initiator.\r
+//\r
+STATIC CONST CHAP_HASH mChapHash[] = {\r
+  {\r
+    ISCSI_CHAP_ALGORITHM_MD5,\r
+    MD5_DIGEST_SIZE,\r
+    Md5GetContextSize,\r
+    Md5Init,\r
+    Md5Update,\r
+    Md5Final\r
+  },\r
+};\r
+\r
+//\r
+// Ordered list of mChapHash[*].Algorithm values. It is formatted for the\r
+// CHAP_A=<A1,A2...> value string, by the IScsiCHAPInitHashList() function. It\r
+// is sent by the initiator in ISCSI_CHAP_STEP_ONE.\r
+//\r
+STATIC CHAR8 mChapHashListString[\r
+               3 +                                      // UINT8 identifier in\r
+                                                        //   decimal\r
+               (1 + 3) * (ARRAY_SIZE (mChapHash) - 1) + // comma prepended for\r
+                                                        //   entries after the\r
+                                                        //   first\r
+               1 +                                      // extra character for\r
+                                                        //   AsciiSPrint()\r
+                                                        //   truncation check\r
+               1                                        // terminating NUL\r
+               ];\r
+\r
 /**\r
-  Initator caculates its own expected hash value.\r
-  \r
+  Initiator calculates its own expected hash value.\r
+\r
   @param[in]   ChapIdentifier     iSCSI CHAP identifier sent by authenticator.\r
   @param[in]   ChapSecret         iSCSI CHAP secret of the authenticator.\r
   @param[in]   SecretLength       The length of iSCSI CHAP secret.\r
   @param[in]   ChapChallenge      The challenge message sent by authenticator.\r
   @param[in]   ChallengeLength    The length of iSCSI CHAP challenge message.\r
+  @param[in]   Hash               Pointer to the CHAP_HASH structure that\r
+                                  determines the hashing algorithm to use. The\r
+                                  caller is responsible for making Hash point\r
+                                  to an "mChapHash" element.\r
   @param[out]  ChapResponse       The calculation of the expected hash value.\r
-  \r
-  @retval EFI_SUCCESS             The expected hash value was caculatedly successfully.\r
-  @retval EFI_PROTOCOL_ERROR      The length of the secret should be at least the\r
-                                  length of the hash value for the hashing algorithm chosen.\r
-  @retval EFI_PROTOCOL_ERROR      MD5 hash operation fail.\r
-  @retval EFI_OUT_OF_RESOURCES    Fail to allocate resource to complete MD5.\r
+\r
+  @retval EFI_SUCCESS             The expected hash value was calculatedly\r
+                                  successfully.\r
+  @retval EFI_PROTOCOL_ERROR      The length of the secret should be at least\r
+                                  the length of the hash value for the hashing\r
+                                  algorithm chosen.\r
+  @retval EFI_PROTOCOL_ERROR      Hash operation fails.\r
+  @retval EFI_OUT_OF_RESOURCES    Failure to allocate resource to complete\r
+                                  hashing.\r
 \r
 **/\r
 EFI_STATUS\r
 IScsiCHAPCalculateResponse (\r
-  IN  UINT32  ChapIdentifier,\r
-  IN  CHAR8   *ChapSecret,\r
-  IN  UINT32  SecretLength,\r
-  IN  UINT8   *ChapChallenge,\r
-  IN  UINT32  ChallengeLength,\r
-  OUT UINT8   *ChapResponse\r
+  IN  UINT32          ChapIdentifier,\r
+  IN  CHAR8           *ChapSecret,\r
+  IN  UINT32          SecretLength,\r
+  IN  UINT8           *ChapChallenge,\r
+  IN  UINT32          ChallengeLength,\r
+  IN  CONST CHAP_HASH *Hash,\r
+  OUT UINT8           *ChapResponse\r
   )\r
 {\r
-  UINTN       Md5ContextSize;\r
-  VOID        *Md5Ctx;\r
+  UINTN       ContextSize;\r
+  VOID        *Ctx;\r
   CHAR8       IdByte[1];\r
   EFI_STATUS  Status;\r
 \r
@@ -50,15 +86,17 @@ IScsiCHAPCalculateResponse (
     return EFI_PROTOCOL_ERROR;\r
   }\r
 \r
-  Md5ContextSize = Md5GetContextSize ();\r
-  Md5Ctx = AllocatePool (Md5ContextSize);\r
-  if (Md5Ctx == NULL) {\r
+  ASSERT (Hash != NULL);\r
+\r
+  ContextSize = Hash->GetContextSize ();\r
+  Ctx = AllocatePool (ContextSize);\r
+  if (Ctx == NULL) {\r
     return EFI_OUT_OF_RESOURCES;\r
   }\r
 \r
   Status = EFI_PROTOCOL_ERROR;\r
 \r
-  if (!Md5Init (Md5Ctx)) {\r
+  if (!Hash->Init (Ctx)) {\r
     goto Exit;\r
   }\r
 \r
@@ -66,42 +104,44 @@ IScsiCHAPCalculateResponse (
   // Hash Identifier - Only calculate 1 byte data (RFC1994)\r
   //\r
   IdByte[0] = (CHAR8) ChapIdentifier;\r
-  if (!Md5Update (Md5Ctx, IdByte, 1)) {\r
+  if (!Hash->Update (Ctx, IdByte, 1)) {\r
     goto Exit;\r
   }\r
 \r
   //\r
   // Hash Secret\r
   //\r
-  if (!Md5Update (Md5Ctx, ChapSecret, SecretLength)) {\r
+  if (!Hash->Update (Ctx, ChapSecret, SecretLength)) {\r
     goto Exit;\r
   }\r
 \r
   //\r
   // Hash Challenge received from Target\r
   //\r
-  if (!Md5Update (Md5Ctx, ChapChallenge, ChallengeLength)) {\r
+  if (!Hash->Update (Ctx, ChapChallenge, ChallengeLength)) {\r
     goto Exit;\r
   }\r
 \r
-  if (Md5Final (Md5Ctx, ChapResponse)) {\r
+  if (Hash->Final (Ctx, ChapResponse)) {\r
     Status = EFI_SUCCESS;\r
   }\r
 \r
 Exit:\r
-  FreePool (Md5Ctx);\r
+  FreePool (Ctx);\r
   return Status;\r
 }\r
 \r
 /**\r
-  The initator checks the CHAP response replied by target against its own\r
-  calculation of the expected hash value. \r
-  \r
-  @param[in]   AuthData             iSCSI CHAP authentication data. \r
-  @param[in]   TargetResponse       The response from target.    \r
-\r
-  @retval EFI_SUCCESS               The response from target passed authentication.\r
-  @retval EFI_SECURITY_VIOLATION    The response from target was not expected value.\r
+  The initiator checks the CHAP response replied by target against its own\r
+  calculation of the expected hash value.\r
+\r
+  @param[in]   AuthData             iSCSI CHAP authentication data.\r
+  @param[in]   TargetResponse       The response from target.\r
+\r
+  @retval EFI_SUCCESS               The response from target passed\r
+                                    authentication.\r
+  @retval EFI_SECURITY_VIOLATION    The response from target was not expected\r
+                                    value.\r
   @retval Others                    Other errors as indicated.\r
 \r
 **/\r
@@ -113,21 +153,31 @@ IScsiCHAPAuthTarget (
 {\r
   EFI_STATUS  Status;\r
   UINT32      SecretSize;\r
-  UINT8       VerifyRsp[ISCSI_CHAP_RSP_LEN];\r
+  UINT8       VerifyRsp[ISCSI_CHAP_MAX_DIGEST_SIZE];\r
+  INTN        Mismatch;\r
 \r
   Status      = EFI_SUCCESS;\r
 \r
   SecretSize  = (UINT32) AsciiStrLen (AuthData->AuthConfig->ReverseCHAPSecret);\r
+\r
+  ASSERT (AuthData->Hash != NULL);\r
+\r
   Status = IScsiCHAPCalculateResponse (\r
              AuthData->OutIdentifier,\r
              AuthData->AuthConfig->ReverseCHAPSecret,\r
              SecretSize,\r
              AuthData->OutChallenge,\r
-             AuthData->OutChallengeLength,\r
+             AuthData->Hash->DigestSize,              // ChallengeLength\r
+             AuthData->Hash,\r
              VerifyRsp\r
              );\r
 \r
-  if (CompareMem (VerifyRsp, TargetResponse, ISCSI_CHAP_RSP_LEN) != 0) {\r
+  Mismatch = CompareMem (\r
+               VerifyRsp,\r
+               TargetResponse,\r
+               AuthData->Hash->DigestSize\r
+               );\r
+  if (Mismatch != 0) {\r
     Status = EFI_SECURITY_VIOLATION;\r
   }\r
 \r
@@ -164,9 +214,10 @@ IScsiCHAPOnRspReceived (
   CHAR8                       *Challenge;\r
   CHAR8                       *Name;\r
   CHAR8                       *Response;\r
-  UINT8                       TargetRsp[ISCSI_CHAP_RSP_LEN];\r
+  UINT8                       TargetRsp[ISCSI_CHAP_MAX_DIGEST_SIZE];\r
   UINT32                      RspLen;\r
   UINTN                       Result;\r
+  UINTN                       HashIndex;\r
 \r
   ASSERT (Conn->CurrentStage == ISCSI_SECURITY_NEGOTIATION);\r
   ASSERT (Conn->RspQue.BufNum != 0);\r
@@ -199,7 +250,10 @@ IScsiCHAPOnRspReceived (
     //\r
     // The first Login Response.\r
     //\r
-    Value = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_TARGET_PORTAL_GROUP_TAG);\r
+    Value = IScsiGetValueByKeyFromList (\r
+              KeyValueList,\r
+              ISCSI_KEY_TARGET_PORTAL_GROUP_TAG\r
+              );\r
     if (Value == NULL) {\r
       goto ON_EXIT;\r
     }\r
@@ -211,13 +265,17 @@ IScsiCHAPOnRspReceived (
 \r
     Session->TargetPortalGroupTag = (UINT16) Result;\r
 \r
-    Value                         = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_AUTH_METHOD);\r
+    Value                         = IScsiGetValueByKeyFromList (\r
+                                      KeyValueList,\r
+                                      ISCSI_KEY_AUTH_METHOD\r
+                                      );\r
     if (Value == NULL) {\r
       goto ON_EXIT;\r
     }\r
     //\r
-    // Initiator mandates CHAP authentication but target replies without "CHAP", or\r
-    // initiator suggets "None" but target replies with some kind of auth method.\r
+    // Initiator mandates CHAP authentication but target replies without\r
+    // "CHAP", or initiator suggets "None" but target replies with some kind of\r
+    // auth method.\r
     //\r
     if (Session->AuthType == ISCSI_AUTH_TYPE_NONE) {\r
       if (AsciiStrCmp (Value, ISCSI_KEY_VALUE_NONE) != 0) {\r
@@ -242,46 +300,74 @@ IScsiCHAPOnRspReceived (
     //\r
     // The Target replies with CHAP_A=<A> CHAP_I=<I> CHAP_C=<C>\r
     //\r
-    Value = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_CHAP_ALGORITHM);\r
+    Value = IScsiGetValueByKeyFromList (\r
+              KeyValueList,\r
+              ISCSI_KEY_CHAP_ALGORITHM\r
+              );\r
     if (Value == NULL) {\r
       goto ON_EXIT;\r
     }\r
 \r
     Algorithm = IScsiNetNtoi (Value);\r
-    if (Algorithm != ISCSI_CHAP_ALGORITHM_MD5) {\r
+    for (HashIndex = 0; HashIndex < ARRAY_SIZE (mChapHash); HashIndex++) {\r
+      if (Algorithm == mChapHash[HashIndex].Algorithm) {\r
+        break;\r
+      }\r
+    }\r
+    if (HashIndex == ARRAY_SIZE (mChapHash)) {\r
       //\r
       // Unsupported algorithm is chosen by target.\r
       //\r
       goto ON_EXIT;\r
     }\r
+    //\r
+    // Remember the target's chosen hash algorithm.\r
+    //\r
+    ASSERT (AuthData->Hash == NULL);\r
+    AuthData->Hash = &mChapHash[HashIndex];\r
 \r
-    Identifier = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_CHAP_IDENTIFIER);\r
+    Identifier = IScsiGetValueByKeyFromList (\r
+                   KeyValueList,\r
+                   ISCSI_KEY_CHAP_IDENTIFIER\r
+                   );\r
     if (Identifier == NULL) {\r
       goto ON_EXIT;\r
     }\r
 \r
-    Challenge = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_CHAP_CHALLENGE);\r
+    Challenge = IScsiGetValueByKeyFromList (\r
+                  KeyValueList,\r
+                  ISCSI_KEY_CHAP_CHALLENGE\r
+                  );\r
     if (Challenge == NULL) {\r
       goto ON_EXIT;\r
     }\r
     //\r
     // Process the CHAP identifier and CHAP Challenge from Target.\r
     // Calculate Response value.\r
-    //    \r
+    //\r
     Result = IScsiNetNtoi (Identifier);\r
     if (Result > 0xFF) {\r
       goto ON_EXIT;\r
-    }    \r
-    \r
+    }\r
+\r
     AuthData->InIdentifier      = (UINT32) Result;\r
-    AuthData->InChallengeLength = ISCSI_CHAP_AUTH_MAX_LEN;\r
-    IScsiHexToBin ((UINT8 *) AuthData->InChallenge, &AuthData->InChallengeLength, Challenge);\r
+    AuthData->InChallengeLength = (UINT32) sizeof (AuthData->InChallenge);\r
+    Status = IScsiHexToBin (\r
+               (UINT8 *) AuthData->InChallenge,\r
+               &AuthData->InChallengeLength,\r
+               Challenge\r
+               );\r
+    if (EFI_ERROR (Status)) {\r
+      Status = EFI_PROTOCOL_ERROR;\r
+      goto ON_EXIT;\r
+    }\r
     Status = IScsiCHAPCalculateResponse (\r
                AuthData->InIdentifier,\r
                AuthData->AuthConfig->CHAPSecret,\r
                (UINT32) AsciiStrLen (AuthData->AuthConfig->CHAPSecret),\r
                AuthData->InChallenge,\r
                AuthData->InChallengeLength,\r
+               AuthData->Hash,\r
                AuthData->CHAPResponse\r
                );\r
 \r
@@ -309,13 +395,21 @@ IScsiCHAPOnRspReceived (
       goto ON_EXIT;\r
     }\r
 \r
-    Response = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_CHAP_RESPONSE);\r
+    Response = IScsiGetValueByKeyFromList (\r
+                 KeyValueList,\r
+                 ISCSI_KEY_CHAP_RESPONSE\r
+                 );\r
     if (Response == NULL) {\r
       goto ON_EXIT;\r
     }\r
 \r
-    RspLen = ISCSI_CHAP_RSP_LEN;\r
-    IScsiHexToBin (TargetRsp, &RspLen, Response);\r
+    ASSERT (AuthData->Hash != NULL);\r
+    RspLen = AuthData->Hash->DigestSize;\r
+    Status = IScsiHexToBin (TargetRsp, &RspLen, Response);\r
+    if (EFI_ERROR (Status) || RspLen != AuthData->Hash->DigestSize) {\r
+      Status = EFI_PROTOCOL_ERROR;\r
+      goto ON_EXIT;\r
+    }\r
 \r
     //\r
     // Check the CHAP Name and Response replied by Target.\r
@@ -331,7 +425,7 @@ ON_EXIT:
 \r
   if (KeyValueList != NULL) {\r
     IScsiFreeKeyValueList (KeyValueList);\r
-  }  \r
+  }\r
 \r
   FreePool (Data);\r
 \r
@@ -347,7 +441,8 @@ ON_EXIT:
   @param[in, out]  Pdu         The PDU to send out.\r
 \r
   @retval EFI_SUCCESS           All check passed and the phase-related CHAP\r
-                                authentication info is filled into the iSCSI PDU.\r
+                                authentication info is filled into the iSCSI\r
+                                PDU.\r
   @retval EFI_OUT_OF_RESOURCES  Failed to allocate memory.\r
   @retval EFI_PROTOCOL_ERROR    Some kind of protocol error occurred.\r
 \r
@@ -368,21 +463,25 @@ IScsiCHAPToSendReq (
   UINT32                      RspLen;\r
   CHAR8                       *Challenge;\r
   UINT32                      ChallengeLen;\r
+  EFI_STATUS                  BinToHexStatus;\r
 \r
   ASSERT (Conn->CurrentStage == ISCSI_SECURITY_NEGOTIATION);\r
 \r
   Session     = Conn->Session;\r
   AuthData    = &Session->AuthData.CHAP;\r
   LoginReq    = (ISCSI_LOGIN_REQUEST *) NetbufGetByte (Pdu, 0, 0);\r
+  if (LoginReq == NULL) {\r
+    return EFI_PROTOCOL_ERROR;\r
+  }\r
   Status      = EFI_SUCCESS;\r
 \r
-  RspLen      = 2 * ISCSI_CHAP_RSP_LEN + 3;\r
+  RspLen      = 2 * ISCSI_CHAP_MAX_DIGEST_SIZE + 3;\r
   Response    = AllocateZeroPool (RspLen);\r
   if (Response == NULL) {\r
     return EFI_OUT_OF_RESOURCES;\r
   }\r
 \r
-  ChallengeLen  = 2 * ISCSI_CHAP_RSP_LEN + 3;\r
+  ChallengeLen  = 2 * ISCSI_CHAP_MAX_DIGEST_SIZE + 3;\r
   Challenge     = AllocateZeroPool (ChallengeLen);\r
   if (Challenge == NULL) {\r
     FreePool (Response);\r
@@ -395,7 +494,11 @@ IScsiCHAPToSendReq (
     // It's the initial Login Request. Fill in the key=value pairs mandatory\r
     // for the initial Login Request.\r
     //\r
-    IScsiAddKeyValuePair (Pdu, ISCSI_KEY_INITIATOR_NAME, mPrivate->InitiatorName);\r
+    IScsiAddKeyValuePair (\r
+      Pdu,\r
+      ISCSI_KEY_INITIATOR_NAME,\r
+      mPrivate->InitiatorName\r
+      );\r
     IScsiAddKeyValuePair (Pdu, ISCSI_KEY_SESSION_TYPE, "Normal");\r
     IScsiAddKeyValuePair (\r
       Pdu,\r
@@ -416,10 +519,10 @@ IScsiCHAPToSendReq (
 \r
   case ISCSI_CHAP_STEP_ONE:\r
     //\r
-    // First step, send the Login Request with CHAP_A=<A1,A2...> key-value pair.\r
+    // First step, send the Login Request with CHAP_A=<A1,A2...> key-value\r
+    // pair.\r
     //\r
-    AsciiSPrint (ValueStr, sizeof (ValueStr), "%d", ISCSI_CHAP_ALGORITHM_MD5);\r
-    IScsiAddKeyValuePair (Pdu, ISCSI_KEY_CHAP_ALGORITHM, ValueStr);\r
+    IScsiAddKeyValuePair (Pdu, ISCSI_KEY_CHAP_ALGORITHM, mChapHashListString);\r
 \r
     Conn->AuthStep = ISCSI_CHAP_STEP_TWO;\r
     break;\r
@@ -432,11 +535,22 @@ IScsiCHAPToSendReq (
     //\r
     // CHAP_N=<N>\r
     //\r
-    IScsiAddKeyValuePair (Pdu, ISCSI_KEY_CHAP_NAME, (CHAR8 *) &AuthData->AuthConfig->CHAPName);\r
+    IScsiAddKeyValuePair (\r
+      Pdu,\r
+      ISCSI_KEY_CHAP_NAME,\r
+      (CHAR8 *) &AuthData->AuthConfig->CHAPName\r
+      );\r
     //\r
     // CHAP_R=<R>\r
     //\r
-    IScsiBinToHex ((UINT8 *) AuthData->CHAPResponse, ISCSI_CHAP_RSP_LEN, Response, &RspLen);\r
+    ASSERT (AuthData->Hash != NULL);\r
+    BinToHexStatus = IScsiBinToHex (\r
+                       (UINT8 *) AuthData->CHAPResponse,\r
+                       AuthData->Hash->DigestSize,\r
+                       Response,\r
+                       &RspLen\r
+                       );\r
+    ASSERT_EFI_ERROR (BinToHexStatus);\r
     IScsiAddKeyValuePair (Pdu, ISCSI_KEY_CHAP_RESPONSE, Response);\r
 \r
     if (AuthData->AuthConfig->CHAPType == ISCSI_CHAP_MUTUAL) {\r
@@ -449,9 +563,17 @@ IScsiCHAPToSendReq (
       //\r
       // CHAP_C=<C>\r
       //\r
-      IScsiGenRandom ((UINT8 *) AuthData->OutChallenge, ISCSI_CHAP_RSP_LEN);\r
-      AuthData->OutChallengeLength = ISCSI_CHAP_RSP_LEN;\r
-      IScsiBinToHex ((UINT8 *) AuthData->OutChallenge, ISCSI_CHAP_RSP_LEN, Challenge, &ChallengeLen);\r
+      IScsiGenRandom (\r
+        (UINT8 *) AuthData->OutChallenge,\r
+        AuthData->Hash->DigestSize\r
+        );\r
+      BinToHexStatus = IScsiBinToHex (\r
+                         (UINT8 *) AuthData->OutChallenge,\r
+                         AuthData->Hash->DigestSize,\r
+                         Challenge,\r
+                         &ChallengeLen\r
+                         );\r
+      ASSERT_EFI_ERROR (BinToHexStatus);\r
       IScsiAddKeyValuePair (Pdu, ISCSI_KEY_CHAP_CHALLENGE, Challenge);\r
 \r
       Conn->AuthStep = ISCSI_CHAP_STEP_FOUR;\r
@@ -472,3 +594,60 @@ IScsiCHAPToSendReq (
 \r
   return Status;\r
 }\r
+\r
+/**\r
+  Initialize the CHAP_A=<A1,A2...> *value* string for the entire driver, to be\r
+  sent by the initiator in ISCSI_CHAP_STEP_ONE.\r
+\r
+  This function sanity-checks the internal table of supported CHAP hashing\r
+  algorithms, as well.\r
+**/\r
+VOID\r
+IScsiCHAPInitHashList (\r
+  VOID\r
+  )\r
+{\r
+  CHAR8           *Position;\r
+  UINTN           Left;\r
+  UINTN           HashIndex;\r
+  CONST CHAP_HASH *Hash;\r
+  UINTN           Printed;\r
+\r
+  Position = mChapHashListString;\r
+  Left = sizeof (mChapHashListString);\r
+  for (HashIndex = 0; HashIndex < ARRAY_SIZE (mChapHash); HashIndex++) {\r
+    Hash = &mChapHash[HashIndex];\r
+\r
+    //\r
+    // Format the next hash identifier.\r
+    //\r
+    // Assert that we can format at least one non-NUL character, i.e. that we\r
+    // can progress. Truncation is checked after printing.\r
+    //\r
+    ASSERT (Left >= 2);\r
+    Printed = AsciiSPrint (\r
+                Position,\r
+                Left,\r
+                "%a%d",\r
+                (HashIndex == 0) ? "" : ",",\r
+                Hash->Algorithm\r
+                );\r
+    //\r
+    // There's no way to differentiate between the "buffer filled to the brim,\r
+    // but not truncated" result and the "truncated" result of AsciiSPrint().\r
+    // This is why "mChapHashListString" has an extra byte allocated, and the\r
+    // reason why we use the less-than (rather than the less-than-or-equal-to)\r
+    // relational operator in the assertion below -- we enforce "no truncation"\r
+    // by excluding the "completely used up" case too.\r
+    //\r
+    ASSERT (Printed + 1 < Left);\r
+\r
+    Position += Printed;\r
+    Left -= Printed;\r
+\r
+    //\r
+    // Sanity-check the digest size for Hash.\r
+    //\r
+    ASSERT (Hash->DigestSize <= ISCSI_CHAP_MAX_DIGEST_SIZE);\r
+  }\r
+}\r