From: Shenglei Zhang <shenglei.zhang@intel.com>
To: edk2-devel@lists.01.org
Cc: Mike Turner <miketur@microsoft.com>,
Michael D Kinney <michael.d.kinney@intel.com>,
Liming Gao <liming.gao@intel.com>
Subject: [PATCH v3] MdePkg/BaseLib: Add Base64Encode() and Base64Decode()
Date: Tue, 29 Jan 2019 14:53:15 +0800 [thread overview]
Message-ID: <20190129065315.9520-1-shenglei.zhang@intel.com> (raw)
From: Mike Turner <miketur@microsoft.com>
Introduce public functions Base64Encode and Base64Decode.
https://bugzilla.tianocore.org/show_bug.cgi?id=1370
v2:1.Remove some white space.
2.Add unit test with test vectors in RFC 4648.
https://github.com/shenglei10/edk2/tree/encode_test
https://github.com/shenglei10/edk2/tree/decode_test
v3:1.Align white space.
2.Update comments of Base64Encode and Base64Decode.
3.Change the use of macro RETURN_DEVICE_ERROR to
RETURN_INVALID_PARAMETER in string.c.
Cc: Michael D Kinney <michael.d.kinney@intel.com>
Cc: Liming Gao <liming.gao@intel.com>
Contributed-under: TianoCore Contribution Agreement 1.1
Signed-off-by: Shenglei Zhang <shenglei.zhang@intel.com>
---
MdePkg/Include/Library/BaseLib.h | 56 +++++
MdePkg/Library/BaseLib/String.c | 344 +++++++++++++++++++++++++++++++
2 files changed, 400 insertions(+)
diff --git a/MdePkg/Include/Library/BaseLib.h b/MdePkg/Include/Library/BaseLib.h
index e8cc121ddd..1d0751c2fc 100644
--- a/MdePkg/Include/Library/BaseLib.h
+++ b/MdePkg/Include/Library/BaseLib.h
@@ -2760,6 +2760,62 @@ AsciiToUpper (
IN CHAR8 Chr
);
+/**
+ 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 bigger than (MAX_ADDRESS - (UINTN)DataPtr/AsciiPtr).
+ @retval RETURN_BUFFER_TOO_SMALL If DataLen is 0 and AsciiSize is <1.
+ @retval RETURN_BUFFER_TOO_SMALL If AsciiPtr is NULL or AsciiSize is smaller than required buffersize.
+
+**/
+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 bigger than (MAX_ADDRESS -(UINTN)DataPtr/BinPtr ).
+ @retval RETURN_INVALID_PARAMETER If there is any invalid character in input stream.
+ @retval RETURN_BUFFER_TOO_SMALL If buffer length is smaller than required buffer size.
+
+ **/
+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 dba53779c9..2a5c03db04 100644
--- a/MdePkg/Library/BaseLib/String.c
+++ b/MdePkg/Library/BaseLib/String.c
@@ -1188,6 +1188,350 @@ AsciiToUpper (
return (UINT8) ((Chr >= 'a' && Chr <= 'z') ? Chr - ('a' - 'A') : Chr);
}
+//
+// 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 bigger than (MAX_ADDRESS - (UINTN)DataPtr/AsciiPtr).
+ @retval RETURN_BUFFER_TOO_SMALL If DataLen is 0 and AsciiSize is <1.
+ @retval RETURN_BUFFER_TOO_SMALL If AsciiPtr is NULL or AsciiSize is smaller than required buffersize.
+
+**/
+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
+ //
+ 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 bigger than (MAX_ADDRESS -(UINTN)DataPtr/BinPtr ).
+ @retval RETURN_INVALID_PARAMETER If there is any invalid character in input stream.
+ @retval RETURN_BUFFER_TOO_SMALL If buffer length is smaller than required buffer size.
+ **/
+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 because of bad input.\n", BufferSize));
+ return RETURN_INVALID_PARAMETER;
+ }
+
+ //
+ // 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;
+}
+
/**
Convert a ASCII character to numerical value.
--
2.18.0.windows.1
next reply other threads:[~2019-01-29 6:53 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-01-29 6:53 Shenglei Zhang [this message]
2019-01-29 7:29 ` [PATCH v3] MdePkg/BaseLib: Add Base64Encode() and Base64Decode() Ni, Ruiyu
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-list from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20190129065315.9520-1-shenglei.zhang@intel.com \
--to=devel@edk2.groups.io \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox