From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received-SPF: Pass (sender SPF authorized) identity=mailfrom; client-ip=209.85.128.66; helo=mail-wm1-f66.google.com; envelope-from=philmd@redhat.com; receiver=edk2-devel@lists.01.org Received: from mail-wm1-f66.google.com (mail-wm1-f66.google.com [209.85.128.66]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by ml01.01.org (Postfix) with ESMTPS id 63D6721A143EF for ; Mon, 7 Jan 2019 01:55:58 -0800 (PST) Received: by mail-wm1-f66.google.com with SMTP id f188so244764wmf.5 for ; Mon, 07 Jan 2019 01:55:58 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:subject:to:cc:references:from:openpgp:message-id :date:user-agent:mime-version:in-reply-to:content-language :content-transfer-encoding; bh=9s07WeePy5ZH+OozJHmdybP2+Wd203fv8Zmr6yGqGes=; b=sDM/xbJMFajn3ptH180fEEHcpY/dv9YS4RWbR5nnN9xXP2FUoea8wfyP/x5pgbWCtm HH5pNR9BPAP1zue57eT/z3E3tXGIWni84Q0UrIvg0CVSrtvVYUPof2VlE58oEhqnzqdo hw1N479GYp4LHvyT2qHnGTckJCeRifmU8hzbuBTTQOJQDERHmv5H5xaz7Iii1VwiOIsg pWJF3ZfPciH2bqqhEY4opE9gFrQNXCj2Mja3Kh1vhrvXUCMuZMJYwXmnwqFoqxgzrYqc k/+dvLT/B/HtEzv2bSreHuApv3p5eIEZKiXb98wEpTY4tdBc44jSvD73oLaTf4JBy6If gdaA== X-Gm-Message-State: AJcUukfPLM7w4JGw2LUPlspvrf7AS5ZYekztWMzDmm+B9sy6pMsV4KcA TwY7a0Rrwb3N6ztB7cjBUmuPIQ== X-Google-Smtp-Source: ALg8bN5bU1aWnmDnRAZcaDG+7giEUydDDG/QH4sbko64mvELAtA5msRCSzATHsZgHFHkhSVeJsnlGQ== X-Received: by 2002:a1c:96ce:: with SMTP id y197mr8538380wmd.36.1546854956626; Mon, 07 Jan 2019 01:55:56 -0800 (PST) Received: from [10.201.33.118] ([195.166.127.210]) by smtp.gmail.com with ESMTPSA id v19sm72759833wrd.46.2019.01.07.01.55.55 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 07 Jan 2019 01:55:56 -0800 (PST) To: Shenglei Zhang , edk2-devel@lists.01.org Cc: Michael D Kinney , Liming Gao References: <20190107083547.12052-1-shenglei.zhang@intel.com> From: =?UTF-8?Q?Philippe_Mathieu-Daud=c3=a9?= Openpgp: id=89C1E78F601EE86C867495CBA2A3FD6EDEADC0DE; url=http://pgp.mit.edu/pks/lookup?op=get&search=0xA2A3FD6EDEADC0DE Message-ID: <1f65083a-7f3f-a75a-7b42-629d193b5675@redhat.com> Date: Mon, 7 Jan 2019 10:55:54 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Thunderbird/60.3.1 MIME-Version: 1.0 In-Reply-To: <20190107083547.12052-1-shenglei.zhang@intel.com> Subject: Re: [PATCH] MdePkg/BaseLib: Add Base64Encode() and Base64Decode() X-BeenThere: edk2-devel@lists.01.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: EDK II Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 07 Jan 2019 09:55:58 -0000 Content-Type: text/plain; charset=utf-8 Content-Language: en-US Content-Transfer-Encoding: 7bit Hi Shenglei, On 1/7/19 9:35 AM, Shenglei Zhang wrote: > Introduce public functions Base64Encode and Base64Decode. > https://bugzilla.tianocore.org/show_bug.cgi?id=1370 > > Cc: Michael D Kinney > Cc: Liming Gao > Contributed-under: TianoCore Contribution Agreement 1.1 > Signed-off-by: Shenglei Zhang > --- > MdePkg/Include/Library/BaseLib.h | 56 +++++ > MdePkg/Library/BaseLib/String.c | 345 +++++++++++++++++++++++++++++++ > 2 files changed, 401 insertions(+) > > diff --git a/MdePkg/Include/Library/BaseLib.h b/MdePkg/Include/Library/BaseLib.h > index 1eb842384e..e5bd054875 100644 > --- a/MdePkg/Include/Library/BaseLib.h > +++ b/MdePkg/Include/Library/BaseLib.h > @@ -2720,6 +2720,62 @@ AsciiStrnToUnicodeStrS ( > OUT UINTN *DestinationLength > ); > > +/** > + Convert binary data to a Base64 encoded ascii string based on RFC4648. > + > + Produce a Null-terminated Ascii string in the output buffer specified by AsciiPtr and AsciiSize. > + The Ascii string is produced by converting the data string specified by DataPtr and DataLen. > + > + @param DataPtr Input UINT8 data > + @param DataLen Number of UINT8 bytes of data > + @param AsciiPtr Pointer to output string buffer > + @param AsciiSize Size of ascii buffer. Set to 0 to get the size needed. > + Caller is responsible for passing in buffer of AsciiSize > + > + @retval RETURN_SUCCESS When ascii buffer is filled in. > + @retval RETURN_INVALID_PARAMETER If DataPtr is NULL or AsciiSize is NULL. > + @retval RETURN_INVALID_PARAMETER If DataLen or AsciiSize is too big. > + @retval RETURN_BUFFER_TOO_SMALL If DataLen is 0 and AsciiSize is <1. > + @retval RETURN_BUFFER_TOO_SMALL If AsciiPtr is NULL or too small. > + > +**/ > +RETURN_STATUS > +EFIAPI > +Base64Encode ( > + IN CONST UINT8 *DataPtr, > + IN UINTN DataLen, > + OUT CHAR8 *AsciiPtr OPTIONAL, > + IN OUT UINTN *AsciiSize > + ); > + > +/** > + Convert Base64 ascii string to binary data based on RFC4648. > + > + Produce Null-terminated binary data in the output buffer specified by BinPtr and BinSize. > + The binary data is produced by converting the Base64 ascii string specified by DataPtr and DataLen. > + > + @param DataPtr Input ASCII characters > + @param DataLen Number of ASCII characters > + @param BinPtr Pointer to output buffer > + @param BinSize Caller is responsible for passing in buffer of at least BinSize. > + Set 0 to get the size needed. Set to bytes stored on return. > + > + @retval RETURN_SUCCESS When binary buffer is filled in. > + @retval RETURN_INVALID_PARAMETER If DataPtr is NULL or BinSize is NULL. > + @retval RETURN_INVALID_PARAMETER If DataLen or BinSize is too big. > + @retval RETURN_INVALID_PARAMETER If BinPtr NULL and BinSize != 0. > + @retval RETURN_INVALID_PARAMETER If there is any Invalid character in input stream. > + @retval RETURN_BUFFER_TOO_SMALL If Buffer length is too small. > + **/ > +RETURN_STATUS > +EFIAPI > +Base64Decode ( > + IN CONST CHAR8 *DataPtr, > + IN UINTN DataLen, > + OUT UINT8 *BinPtr OPTIONAL, > + IN OUT UINTN *BinSize > + ); > + > /** > Converts an 8-bit value to an 8-bit BCD value. > > diff --git a/MdePkg/Library/BaseLib/String.c b/MdePkg/Library/BaseLib/String.c > index e6df12797d..e491489559 100644 > --- a/MdePkg/Library/BaseLib/String.c > +++ b/MdePkg/Library/BaseLib/String.c > @@ -1763,6 +1763,351 @@ AsciiStrToUnicodeStr ( > > #endif > > +// > +// The basis for Base64 encoding is RFC 4686 https://tools.ietf.org/html/rfc4648 > +// > +// RFC 4686 has a number of MAY and SHOULD cases. This implementation chooses > +// the more restrictive versions for security concerns (see RFC 4686 section 3.3). > +// > +// A invalid character, if encountered during the decode operation, causes the data > +// to be rejected. In addition, the '=' padding character is only allowed at the end > +// of the Base64 encoded string. > +// > +#define BAD_V 99 > + > +STATIC CHAR8 Encoding_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" > + "abcdefghijklmnopqrstuvwxyz" > + "0123456789+/"; > + > +STATIC UINT8 Decoding_table[] = { > + // > + // Valid characters ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ > + // Also, set '=' as a zero for decoding > + // 0 , 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f > + BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, // 0 > + BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, // 10 > + BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, 62, BAD_V, BAD_V, BAD_V, 63, // 20 > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, BAD_V, BAD_V, BAD_V, 0, BAD_V, BAD_V, // 30 > + BAD_V, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 40 > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, // 50 > + BAD_V, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 60 > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, // 70 > + BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, // 80 > + BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, // 90 > + BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, // a0 > + BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, // b0 > + BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, // c0 > + BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, // d0 > + BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, // d0 > + BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V, BAD_V // f0 > +}; > + > +/** > + Convert binary data to a Base64 encoded ascii string based on RFC4648. > + > + Produce a Null-terminated Ascii string in the output buffer specified by AsciiPtr and AsciiSize. > + The Ascii string is produced by converting the data string specified by DataPtr and DataLen. > + > + @param DataPtr Input UINT8 data > + @param DataLen Number of UINT8 bytes of data > + @param AsciiPtr Pointer to output string buffer > + @param AsciiSize Size of ascii buffer. Set to 0 to get the size needed. > + Caller is responsible for passing in buffer of AsciiSize > + > + @retval RETURN_SUCCESS When ascii buffer is filled in. > + @retval RETURN_INVALID_PARAMETER If DataPtr is NULL or AsciiSize is NULL. > + @retval RETURN_INVALID_PARAMETER If DataLen or AsciiSize is too big. > + @retval RETURN_BUFFER_TOO_SMALL If DataLen is 0 and AsciiSize is <1. > + @retval RETURN_BUFFER_TOO_SMALL If AsciiPtr is NULL or too small. > + > +**/ > +RETURN_STATUS > +EFIAPI > +Base64Encode ( > + IN CONST UINT8 *DataPtr, > + IN UINTN DataLen, > + OUT CHAR8 *AsciiPtr OPTIONAL, > + IN OUT UINTN *AsciiSize > + ) > +{ > + > + UINTN RequiredSize; > + UINTN Left; > + CONST UINT8 *Inptr; > + CHAR8 *Outptr; > + > + // > + // Check pointers, and DataLen is valid > + // > + if ((DataPtr == NULL) || (AsciiSize == NULL)) { > + return RETURN_INVALID_PARAMETER; > + } > + > + // > + // Allow for RFC 4648 test vector 1 Can you add unit tests with the test vectors from the RFC? > + // > + if (DataLen == 0) { > + if (*AsciiSize < 1) { > + *AsciiSize = 1; > + return RETURN_BUFFER_TOO_SMALL; > + } > + *AsciiSize = 1; > + *AsciiPtr = '\0'; > + return RETURN_SUCCESS; > + } > + > + // > + // Check if Datalen or AsciiSize is valid > + // > + if ((DataLen >= (MAX_ADDRESS - (UINTN)DataPtr)) || (*AsciiSize >= (MAX_ADDRESS - (UINTN)AsciiPtr))){ > + return RETURN_INVALID_PARAMETER; > + } > + > + // > + // 4 ascii per 3 bytes + NULL > + // > + RequiredSize = ((DataLen + 2) / 3) * 4 + 1; > + if ((AsciiPtr == NULL) || *AsciiSize < RequiredSize) { > + *AsciiSize = RequiredSize; > + return RETURN_BUFFER_TOO_SMALL; > + } > + > + Left = DataLen; > + Outptr = AsciiPtr; > + Inptr = DataPtr; > + > + // > + // Encode 24 bits (three bytes) into 4 ascii characters > + // > + while (Left >= 3) { > + > + *Outptr++ = Encoding_table[( Inptr[0] & 0xfc) >> 2 ]; > + *Outptr++ = Encoding_table[((Inptr[0] & 0x03) << 4) + ((Inptr[1] & 0xf0) >> 4)]; > + *Outptr++ = Encoding_table[((Inptr[1] & 0x0f) << 2) + ((Inptr[2] & 0xc0) >> 6)]; > + *Outptr++ = Encoding_table[( Inptr[2] & 0x3f)]; > + Left -= 3; > + Inptr += 3; > + } > + > + // > + // Handle the remainder, and add padding '=' characters as necessary. > + // > + switch (Left) { > + case 0: > + > + // > + // No bytes Left, done. > + // > + break; > + case 1: > + > + // > + // One more data byte, two pad characters > + // > + *Outptr++ = Encoding_table[( Inptr[0] & 0xfc) >> 2]; > + *Outptr++ = Encoding_table[((Inptr[0] & 0x03) << 4)]; > + *Outptr++ = '='; > + *Outptr++ = '='; > + break; > + case 2: > + > + // > + // Two more data bytes, and one pad character > + // > + *Outptr++ = Encoding_table[( Inptr[0] & 0xfc) >> 2]; > + *Outptr++ = Encoding_table[((Inptr[0] & 0x03) << 4) + ((Inptr[1] & 0xf0) >> 4)]; > + *Outptr++ = Encoding_table[((Inptr[1] & 0x0f) << 2)]; > + *Outptr++ = '='; > + break; > + } > + // > + // Add terminating NULL > + // > + *Outptr = '\0'; > + return RETURN_SUCCESS; > +} > + > +/** > + Convert Base64 ascii string to binary data based on RFC4648. > + > + Produce Null-terminated binary data in the output buffer specified by BinPtr and BinSize. > + The binary data is produced by converting the Base64 ascii string specified by DataPtr and DataLen. > + > + @param DataPtr Input ASCII characters > + @param DataLen Number of ASCII characters > + @param BinPtr Pointer to output buffer > + @param BinSize Caller is responsible for passing in buffer of at least BinSize. > + Set 0 to get the size needed. Set to bytes stored on return. > + > + @retval RETURN_SUCCESS When binary buffer is filled in. > + @retval RETURN_INVALID_PARAMETER If DataPtr is NULL or BinSize is NULL. > + @retval RETURN_INVALID_PARAMETER If DataLen or BinSize is too big. > + @retval RETURN_INVALID_PARAMETER If BinPtr NULL and BinSize != 0. > + @retval RETURN_INVALID_PARAMETER If there is any Invalid character in input stream. > + @retval RETURN_BUFFER_TOO_SMALL If Buffer length is too small. > + **/ > +RETURN_STATUS > +EFIAPI > +Base64Decode ( > + IN CONST CHAR8 *DataPtr, > + IN UINTN DataLen, > + OUT UINT8 *BinPtr OPTIONAL, > + IN OUT UINTN *BinSize > + ) > +{ > + > + UINT8 *BinData; > + UINT32 Value; > + CHAR8 Chr; > + INTN BufferSize; > + UINTN Indx; > + UINTN Ondx; > + UINTN Icnt; > + UINTN ActualDataLen; > + > + // > + // Check pointers are not NULL > + // > + if ((DataPtr == NULL) || (BinSize == NULL)) { > + DEBUG((DEBUG_ERROR, "DataPtr=%p, BinSize=%\n", DataPtr, BinSize)); > + return RETURN_INVALID_PARAMETER; > + } > + > + // > + // Check if Datalen or BinSize is valid > + // > + if ((DataLen >= (MAX_ADDRESS - (UINTN)DataPtr)) || (*BinSize >= (MAX_ADDRESS - (UINTN)BinPtr))){ > + return RETURN_INVALID_PARAMETER; > + } > + > + ActualDataLen = 0; > + BufferSize = 0; > + > + // > + // Determine the actual number of valid characters in the string. > + // All invalid characters except selected white space characters, > + // will cause the Base64 string to be rejected. White space to allow > + // properly formatted XML will be ignored. > + // > + // See section 3.3 of RFC 4648. > + // > + for (Indx = 0; Indx < DataLen; Indx++) { > + > + // > + // '=' is part of the quantum > + // > + if (DataPtr[Indx] == '=') { > + ActualDataLen++; > + BufferSize--; > + > + // > + // Only two '=' characters can be valid. > + // > + if (BufferSize < -2) { > + DEBUG((DEBUG_ERROR, "DataPtr=%p, Invalid = at %d\n", DataPtr, Indx)); > + return RETURN_INVALID_PARAMETER; > + } > + } > + else { > + Chr = DataPtr[Indx]; > + if (BAD_V != Decoding_table[(UINT8) Chr]) { > + > + // > + // The '=' characters are only valid at the end, so any > + // valid character after an '=', will be flagged as an error. > + // > + if (BufferSize < 0) { > + DEBUG((DEBUG_ERROR, "DataPtr=%p, Invalid character %c at %d\n", DataPtr, Chr, Indx)); > + return RETURN_INVALID_PARAMETER; > + } > + ActualDataLen++; > + } > + else { > + > + // > + // The reset of the decoder will ignore all invalid characters allowed here. > + // Ignoring selected white space is useful. In this case, the decoder will > + // ignore ' ', '\t', '\n', and '\r'. > + // > + if ((Chr != ' ') &&(Chr != '\t') &&(Chr != '\n') &&(Chr != '\r')) { > + DEBUG((DEBUG_ERROR, "DataPtr=%p, Invalid character %c at %d\n", DataPtr, Chr, Indx)); > + return RETURN_INVALID_PARAMETER; > + } > + } > + } > + } > + > + // > + // The Base64 character string must be a multiple of 4 character quantums. > + // > + if (ActualDataLen % 4 != 0) { > + DEBUG((DEBUG_ERROR,"DataPtr=%p, Invalid data length %d\n", DataPtr, ActualDataLen)); > + return RETURN_INVALID_PARAMETER; > + } > + > + BufferSize += ActualDataLen / 4 * 3; > + if (BufferSize < 0) { > + DEBUG((DEBUG_ERROR,"BufferSize(%d) is wrong.\n", BufferSize)); > + return RETURN_DEVICE_ERROR; > + } > + > + // > + // BufferSize is >= 0 > + // > + if ((BinPtr == NULL) || (*BinSize < (UINTN) BufferSize)) { > + *BinSize = BufferSize; > + return RETURN_BUFFER_TOO_SMALL; > + } > + > + // > + // If no decodable characters, return a size of zero. RFC 4686 test vector 1. > + // > + if (ActualDataLen == 0) { > + *BinSize = 0; > + return RETURN_SUCCESS; > + } > + > + BinData = BinPtr; > + > + // > + // Input data is verified to be a multiple of 4 valid charcters. Process four > + // characters at a time. Uncounted (ie. invalid) characters will be ignored. > + // > + for (Indx = 0, Ondx = 0; (Indx < DataLen) && (Ondx < *BinSize); ) { > + Value = 0; > + > + // > + // Get 24 bits of data from 4 input characters, each character representing 6 bits > + // > + for (Icnt = 0; Icnt < 4; Icnt++) { > + do { > + Chr = Decoding_table[(UINT8) DataPtr[Indx++]]; > + } while (Chr == BAD_V); > + Value <<= 6; > + Value |= Chr; > + } > + > + // > + // Store 3 bytes of binary data (24 bits) > + // > + *BinData++ = (UINT8) (Value >> 16); > + Ondx++; > + > + // > + // Due to the '=' special cases for the two bytes at the end, > + // we have to check the length and not store the padding data > + // > + if (Ondx++ < *BinSize) { > + *BinData++ = (UINT8) (Value >> 8); > + } > + if (Ondx++ < *BinSize) { > + *BinData++ = (UINT8) Value; > + } > + } > + > + return RETURN_SUCCESS; > +} > + > /** > Converts an 8-bit value to an 8-bit BCD value. > >