From 3706fabc10e0f9241005403f25bcadda9e39bdda Mon Sep 17 00:00:00 2001 From: DCCooper <1866858@gmail.com> Date: Thu, 23 Dec 2021 20:45:38 +0800 Subject: [PATCH] isula-build: sync upstream patches Signed-off-by: DCCooper <1866858@gmail.com> --- VERSION-openeuler | 2 +- git-commit | 2 +- isula-build.spec | 8 +- ...ts-fix-testcase-TestPrepareFromImage.patch | 170 +++++++++++++ ...some-little-mistakes-in-manual_zh.md.patch | 60 +++++ ...is-not-rational-when-not-appoint-Doc.patch | 110 +++++++++ ...remove-unused-PBKDF2-and-AES-related.patch | 233 ++++++++++++++++++ series.conf | 4 + 8 files changed, 586 insertions(+), 3 deletions(-) create mode 100644 patch/0095-tests-fix-testcase-TestPrepareFromImage.patch create mode 100644 patch/0096-fix-some-little-mistakes-in-manual_zh.md.patch create mode 100644 patch/0097-fix-the-message-is-not-rational-when-not-appoint-Doc.patch create mode 100644 patch/0098-utils-remove-unused-PBKDF2-and-AES-related.patch diff --git a/VERSION-openeuler b/VERSION-openeuler index edac318..6fa1f2b 100644 --- a/VERSION-openeuler +++ b/VERSION-openeuler @@ -1 +1 @@ -0.9.6-2 +0.9.6-3 diff --git a/git-commit b/git-commit index 0dcce9b..564803b 100644 --- a/git-commit +++ b/git-commit @@ -1 +1 @@ -01a8133f799d0ec83cbe4bbcedd4a14ea7ff3a7a +2f8e5cc49d62d2f1c1ac161e9f3156b9f927084e diff --git a/isula-build.spec b/isula-build.spec index 7fc5454..99981c1 100644 --- a/isula-build.spec +++ b/isula-build.spec @@ -2,7 +2,7 @@ Name: isula-build Version: 0.9.6 -Release: 2 +Release: 3 Summary: A tool to build container images License: Mulan PSL V2 URL: https://gitee.com/openeuler/isula-build @@ -85,6 +85,12 @@ fi /usr/share/bash-completion/completions/isula-build %changelog +* Thu Dec 23 2021 DCCooper <1866858@gmail.com> - 0.9.6-3 +- Type:bugfix +- CVE:NA +- SUG:restart +- DESC:sync upstream patches + * Wed Dec 08 2021 DCCooper <1866858@gmail.com> - 0.9.6-2 - Type:enhancement - CVE:NA diff --git a/patch/0095-tests-fix-testcase-TestPrepareFromImage.patch b/patch/0095-tests-fix-testcase-TestPrepareFromImage.patch new file mode 100644 index 0000000..27fd356 --- /dev/null +++ b/patch/0095-tests-fix-testcase-TestPrepareFromImage.patch @@ -0,0 +1,170 @@ +From b79ac03734ac9fdd36b6a8a738a43c617fd31b9a Mon Sep 17 00:00:00 2001 +From: Lu Jingxiao +Date: Sat, 11 Dec 2021 11:18:56 +0800 +Subject: [PATCH 1/4] tests: fix testcase TestPrepareFromImage + +Test case TestPrepareFromImage fails randomly for rand.Int() does not +reset Seeds before using. + +Fixes: #I4M25L + +Signed-off-by: Lu Jingxiao +--- + builder/dockerfile/add_copy_test.go | 15 +++++++-------- + builder/dockerfile/stage_builder_test.go | 5 ++--- + util/test_util.go | 9 +++++++++ + util/user_test.go | 4 ++-- + util/util_test.go | 5 ++--- + 5 files changed, 22 insertions(+), 16 deletions(-) + +diff --git a/builder/dockerfile/add_copy_test.go b/builder/dockerfile/add_copy_test.go +index 8873872a..36dd66a6 100644 +--- a/builder/dockerfile/add_copy_test.go ++++ b/builder/dockerfile/add_copy_test.go +@@ -16,7 +16,6 @@ package dockerfile + import ( + "fmt" + "io/ioutil" +- "math/rand" + "os" + "os/exec" + "path/filepath" +@@ -253,8 +252,8 @@ func TestResolveCopySource(t *testing.T) { + } + + func TestAddFile(t *testing.T) { +- realSrc := fmt.Sprintf("/tmp/test-%d", rand.Int()) +- dest := fmt.Sprintf("/tmp/test2-%d", rand.Int()) ++ realSrc := fmt.Sprintf("/tmp/test-%d", util.GenRandInt64()) ++ dest := fmt.Sprintf("/tmp/test2-%d", util.GenRandInt64()) + err := exec.Command("/bin/sh", "-c", "touch "+realSrc).Run() + assert.NilError(t, err) + +@@ -269,9 +268,9 @@ func TestAddFile(t *testing.T) { + err = os.Remove(dest) + assert.NilError(t, err) + +- tarFile := fmt.Sprintf("/tmp/a-%d.tar.gz", rand.Int()) +- srcFile1 := fmt.Sprintf("/tmp/test-%d", rand.Int()) +- srcFile2 := fmt.Sprintf("/tmp/test2-%d", rand.Int()) ++ tarFile := fmt.Sprintf("/tmp/a-%d.tar.gz", util.GenRandInt64()) ++ srcFile1 := fmt.Sprintf("/tmp/test-%d", util.GenRandInt64()) ++ srcFile2 := fmt.Sprintf("/tmp/test2-%d", util.GenRandInt64()) + err = exec.Command("/bin/sh", "-c", "touch "+srcFile1+" "+srcFile2+ + " && tar -czf "+tarFile+" "+srcFile1+" "+srcFile2).Run() + assert.NilError(t, err) +@@ -298,8 +297,8 @@ func TestAddFile(t *testing.T) { + + func TestAdd(t *testing.T) { + ignores := []string{"a", "b"} +- contextDir := fmt.Sprintf("/tmp/context-%d", rand.Int()) +- contextDir2 := fmt.Sprintf("/tmp/context-%d", rand.Int()) ++ contextDir := fmt.Sprintf("/tmp/context-%d", util.GenRandInt64()) ++ contextDir2 := fmt.Sprintf("/tmp/context-%d", util.GenRandInt64()) + matcher, err := util.GetIgnorePatternMatcher(ignores, contextDir, "") + assert.NilError(t, err) + +diff --git a/builder/dockerfile/stage_builder_test.go b/builder/dockerfile/stage_builder_test.go +index 9123bcd9..2c922663 100644 +--- a/builder/dockerfile/stage_builder_test.go ++++ b/builder/dockerfile/stage_builder_test.go +@@ -17,7 +17,6 @@ import ( + "bytes" + "context" + "fmt" +- "math/rand" + "os" + "path/filepath" + "runtime" +@@ -76,8 +75,8 @@ func clean() { + func cleanAndSetDefaultStoreOpt(t *testing.T) { + cleanDefaultStoreOpt(t) + store.SetDefaultStoreOptions(store.DaemonStoreOptions{ +- DataRoot: fmt.Sprintf("/tmp/isula-build/storage-data-%d/", rand.Int()), +- RunRoot: fmt.Sprintf("/tmp/isula-build/storage-run-%d/", rand.Int()), ++ DataRoot: fmt.Sprintf("/tmp/isula-build/storage-data-%d/", util.GenRandInt64()), ++ RunRoot: fmt.Sprintf("/tmp/isula-build/storage-run-%d/", util.GenRandInt64()), + }) + localStore, _ = store.GetStore() + } +diff --git a/util/test_util.go b/util/test_util.go +index 653cfd24..bbe2b256 100644 +--- a/util/test_util.go ++++ b/util/test_util.go +@@ -15,8 +15,11 @@ + package util + + import ( ++ "crypto/rand" + "flag" + "fmt" ++ "math" ++ "math/big" + "os/exec" + "strings" + "testing" +@@ -72,3 +75,9 @@ func Immutable(path string, set bool) error { + } + return nil + } ++ ++// GenRandInt64 is to generate an nondeterministic int64 value ++func GenRandInt64() int64 { ++ val, _ := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) ++ return val.Int64() ++} +diff --git a/util/user_test.go b/util/user_test.go +index d042f164..441dca41 100644 +--- a/util/user_test.go ++++ b/util/user_test.go +@@ -15,7 +15,6 @@ package util + + import ( + "fmt" +- "math/rand" + "os" + "testing" + +@@ -32,7 +31,8 @@ func TestGetChownOptions(t *testing.T) { + GIDWanted int + isErr bool + } +- mountpoint := fmt.Sprintf("/tmp/mount-%d", rand.Int()) ++ ++ mountpoint := fmt.Sprintf("/tmp/mount-%d", GenRandInt64()) + err := os.MkdirAll(mountpoint+"/etc", constant.DefaultSharedDirMode) + assert.NilError(t, err) + pFile, err := os.Create(mountpoint + "/etc/passwd") +diff --git a/util/util_test.go b/util/util_test.go +index db57393b..374a69f9 100644 +--- a/util/util_test.go ++++ b/util/util_test.go +@@ -17,7 +17,6 @@ import ( + "bytes" + "context" + "fmt" +- "math/rand" + "net/http" + "os" + "path/filepath" +@@ -76,7 +75,7 @@ func TestCopyURLResource(t *testing.T) { + } + + func TestCopyFile(t *testing.T) { +- src := fmt.Sprintf("/tmp/test-%d", rand.Int()) ++ src := fmt.Sprintf("/tmp/test-%d", GenRandInt64()) + f, err := os.Create(src) + defer func() { + f.Close() +@@ -101,7 +100,7 @@ func TestCopyFile(t *testing.T) { + _, err = f.Write([]byte("This is a test file.")) + assert.NilError(t, err) + +- dir := fmt.Sprintf("/tmp/test2-%d/", rand.Int()) ++ dir := fmt.Sprintf("/tmp/test2-%d/", GenRandInt64()) + dest := dir + "test" + err = CopyFile(src, dest, idtools.IDPair{}) + defer func() { +-- +2.27.0 + diff --git a/patch/0096-fix-some-little-mistakes-in-manual_zh.md.patch b/patch/0096-fix-some-little-mistakes-in-manual_zh.md.patch new file mode 100644 index 0000000..cf18e80 --- /dev/null +++ b/patch/0096-fix-some-little-mistakes-in-manual_zh.md.patch @@ -0,0 +1,60 @@ +From 631764189a65f1a307947360637f4e5352a0c8e5 Mon Sep 17 00:00:00 2001 +From: hlwqds <545743488@qq.com> +Date: Sun, 12 Dec 2021 14:49:22 +0800 +Subject: [PATCH 2/4] fix some little mistakes in manual_zh.md Fixes: #I4M4A3 + Signed-off-by: hlwqds 545743488@qq.com + +--- + doc/manual_zh.md | 12 +++++------- + 1 file changed, 5 insertions(+), 7 deletions(-) + +diff --git a/doc/manual_zh.md b/doc/manual_zh.md +index e68c77ed..c5303f8d 100644 +--- a/doc/manual_zh.md ++++ b/doc/manual_zh.md +@@ -140,7 +140,7 @@ isula-build采用服务端/客户端模式,其中,isula-build为客户端, + > - isula-build 支持最大 1MiB 的上述配置文件。 + > - isula-build 不支持将持久化工作目录 dataroot 配置在内存盘上,比如 tmpfs。 + > - isula-build 目前仅支持使用overlay2为底层 graphdriver。 +-> - 在设置--group参数前,需保证本地OS已经创建了对应的用户组,且非特权用户已经加入该组。重启isula-builder之后即可使该非特权用户使用isula-build功能。同时,为了保持权限一致性,isula-build的配置文件目录/etc/isula-build的数组也会被设置为--group指定的组。 ++> - 在设置--group参数前,需保证本地OS已经创建了对应的用户组,且非特权用户已经加入该组。重启isula-builder之后即可使该非特权用户使用isula-build功能。同时,为了保持权限一致性,isula-build的配置文件目录/etc/isula-build的属组也会被设置为--group指定的组。 + + ### 管理服务 + +@@ -535,21 +535,19 @@ isula-build ctr-img load可以将isula-build ctr-img save分层导出的镜像 + ``` + isula-build ctr-img load -d IMAGES_DIR [-b BASE_IMAGE] [-l LIB_IMAGE] -i APP_IMAGE + ``` +- +-IMAGE:需要导入的应用镜像名:TAG(不能是镜像ID)。 +- ++ + 支持如下Flags: + + - -d:必选,指定应用分层镜像所在的文件夹。文件夹中至少包含app镜像和完整的manifest文件。可以将base层和lib层文件分别存放,然后通过-b和-l参数指定。 + - -b:可选,指定base层镜像的路径。如果不指定,默认在-d指定的路径中。 + - -l:可选,指定lib层镜像的路径。如果不指定,默认在-d指定的路径中。 +-- -i:必选,指定需要导入的应用镜像名字。 ++- -i:必选,指定需要导入的应用镜像名:TAG(不能是镜像ID)。 + - –no-check:可选,跳过sha256校验。 + + > **说明:** + > + > - 需要输入镜像名的参数,要使用IMAGE_NAME:TAG的方式指明唯一的镜像,因为使用IMAGE_ID或不加TAG可能对应多个镜像,或者在导入导出过程中相同的镜像会有不同的ID,导致偏离用户预期的执行结果。 +-> - 使用–no-check时,会跳过对tarball的sha256校验和检查。放弃对tarball进行校验和检查可能引入不确定因素,用户需明确和接受此类行为可能带来的影响和结果。 ++> - 使用–no-check时,会跳过对tar包的sha256校验和检查。放弃对tar包进行校验和检查可能引入不确定因素,用户需明确和接受此类行为可能带来的影响和结果。 + > - 由于涉及中间状态转换、保存,isula-build运行目录/var/lib/isula-build/需保证容量至少为需要进行分层镜像总大小的两倍。假设需要对A(10MB), B(20MB), C(30MB) 三个镜像进行保存分层镜像,则需要保证/var/lib/isula-build所在磁盘大小为2*(10+20+30)=120M。 + > - 在保存、加载分层镜像时,在计算文件的sha256值时需要将文件读取进入内存中,故并发操作时,会有线性内存消耗。 + +@@ -615,7 +613,7 @@ Storing signatures + Save success with image: 21c3e96ac411 + ``` + +-以下示例导出多个镜像到同一个tarball: ++以下示例导出多个镜像到同一个tar包: + ```sh + $ sudo isula-build ctr-img save busybox:latest nginx:latest -o all.tar + Getting image source signatures +-- +2.27.0 + diff --git a/patch/0097-fix-the-message-is-not-rational-when-not-appoint-Doc.patch b/patch/0097-fix-the-message-is-not-rational-when-not-appoint-Doc.patch new file mode 100644 index 0000000..60d649b --- /dev/null +++ b/patch/0097-fix-the-message-is-not-rational-when-not-appoint-Doc.patch @@ -0,0 +1,110 @@ +From 82608cc6cccf55e3f45b147b282b23c1be7d6cc8 Mon Sep 17 00:00:00 2001 +From: hlwqds <545743488@qq.com> +Date: Tue, 14 Dec 2021 23:29:41 +0800 +Subject: [PATCH 3/4] fix the message is not rational when not appoint + Dockerfile Fixes: #I4MB6N Signed-off-by: hlwqds 545743488@qq.com + +--- + cmd/cli/build.go | 44 +++++++++++++++++++++++++++++-------------- + cmd/cli/build_test.go | 16 ++++++++++++++++ + 2 files changed, 46 insertions(+), 14 deletions(-) + +diff --git a/cmd/cli/build.go b/cmd/cli/build.go +index b0f77654..4b4e6f5e 100644 +--- a/cmd/cli/build.go ++++ b/cmd/cli/build.go +@@ -460,34 +460,50 @@ func readDockerfile() (string, string, error) { + return string(buf), parts[1], nil + } + ++func checkDockerfile(filePath string) error { ++ fileInfo, err := os.Stat(filePath) ++ if err != nil { ++ return err ++ } ++ ++ if !fileInfo.Mode().IsRegular() { ++ return errors.Errorf("file %s should be a regular file", filePath) ++ } ++ if fileInfo.Size() == 0 { ++ return errors.New("file is empty, is it a normal dockerfile?") ++ } ++ if fileInfo.Size() > constant.MaxFileSize { ++ return errors.Errorf("file is too big with size %v, is it a normal dockerfile?", fileInfo.Size()) ++ } ++ return nil ++} ++ + func resolveDockerfilePath() (string, error) { + var resolvedPath = buildOpts.file +- ++ var err error + if buildOpts.file == "" { + // filepath is empty, try to resolve with contextDir+Dockerfile + resolvedPath = path.Join(buildOpts.contextDir, "Dockerfile") ++ err = checkDockerfile(resolvedPath) ++ if err != nil { ++ logrus.Debugf("Stat dockerfile failed with path %s", resolvedPath) ++ return "", err ++ } ++ return resolvedPath, nil + } +- // stat path with origin filepath or contextDir+Dockerfile +- fileInfo, err := os.Stat(resolvedPath) ++ ++ err = checkDockerfile(resolvedPath) + if err != nil { + logrus.Debugf("Stat dockerfile failed with path %s", resolvedPath) + // not found with filepath, try to resolve with contextDir+filepath + resolvedPath = path.Join(buildOpts.contextDir, buildOpts.file) +- fileInfo, err = os.Stat(resolvedPath) ++ err = checkDockerfile(resolvedPath) + if err != nil { + logrus.Debugf("Stat dockerfile failed again with path %s", resolvedPath) +- return "", errors.Wrapf(err, "stat dockerfile failed with filename %s", buildOpts.file) ++ return "", err + } + } +- if !fileInfo.Mode().IsRegular() { +- return "", errors.Errorf("file %s should be a regular file", resolvedPath) +- } +- if fileInfo.Size() == 0 { +- return "", errors.New("file is empty, is it a normal dockerfile?") +- } +- if fileInfo.Size() > constant.MaxFileSize { +- return "", errors.Errorf("file is too big with size %v, is it a normal dockerfile?", fileInfo.Size()) +- } ++ + return resolvedPath, nil + } + +diff --git a/cmd/cli/build_test.go b/cmd/cli/build_test.go +index a7fe64e5..7faa1259 100644 +--- a/cmd/cli/build_test.go ++++ b/cmd/cli/build_test.go +@@ -325,6 +325,22 @@ func TestReadDockerfileWithDirectory(t *testing.T) { + assert.ErrorContains(t, err, "should be a regular file") + } + ++// Test readDockerfile ++// case 6. buildOpts.file not appointed and contextDir has no file named Dockerfile ++// expect: return error with Dockerfile(default file name) ++func TestReadDockerfileWithNoNameAndNoFileNamedDockerfile(t *testing.T) { ++ tmpDir := fs.NewDir(t, t.Name()) ++ defer tmpDir.Remove() ++ ++ buildOpts.contextDir = tmpDir.Path() ++ buildOpts.file = "" ++ ++ _, _, err := readDockerfile() ++ // if not found, os.Stat will tell us Dockerfile not found ++ // so it depends on os.Stat's return ++ assert.ErrorContains(t, err, "Dockerfile: no such file or directory") ++} ++ + func TestNewBuildOptions(t *testing.T) { + // no args case use current working directory as context directory + cwd, err := os.Getwd() +-- +2.27.0 + diff --git a/patch/0098-utils-remove-unused-PBKDF2-and-AES-related.patch b/patch/0098-utils-remove-unused-PBKDF2-and-AES-related.patch new file mode 100644 index 0000000..03265ea --- /dev/null +++ b/patch/0098-utils-remove-unused-PBKDF2-and-AES-related.patch @@ -0,0 +1,233 @@ +From eaaca9cb5962a28e6f546e8c0ce1049f5db5d46b Mon Sep 17 00:00:00 2001 +From: jingxiaolu +Date: Wed, 15 Dec 2021 16:42:12 +0800 +Subject: [PATCH 4/4] utils: remove unused PBKDF2 and AES related + +Fixes: #I4MO1B + +Signed-off-by: jingxiaolu +--- + util/cipher.go | 103 -------------------------------------------- + util/cipher_test.go | 75 -------------------------------- + 2 files changed, 178 deletions(-) + +diff --git a/util/cipher.go b/util/cipher.go +index 67cb52bb..fa0559ae 100644 +--- a/util/cipher.go ++++ b/util/cipher.go +@@ -16,8 +16,6 @@ package util + import ( + "bufio" + "crypto" +- "crypto/aes" +- "crypto/cipher" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" +@@ -32,117 +30,16 @@ import ( + "path/filepath" + + "github.com/pkg/errors" +- "golang.org/x/crypto/pbkdf2" +- + constant "isula.org/isula-build" + ) + + const ( +- // CryptoKeyLen is secure key length for aes encryption and decryption(AES-256) +- CryptoKeyLen = 32 +- // iteration is iteration count to hash +- iteration = 409600 +- aesKeyLenUpperBound = 32 +- aesKeyLenLowerBound = 16 + // DefaultRSAKeySize is secure key length for RSA + DefaultRSAKeySize = 2048 + // DefaultRSAKeyPath is the default directory to store rsa public key + DefaultRSAKeyPath = "/etc/isula-build/isula-build.pub" + ) + +-var ( +- errGenCryptoKey = errors.New("generate crypto key failed") +-) +- +-// GenerateCryptoKey generates a random key with length s +-// if used with AES, the input length can only be 16, 24, 32, +-// which stands for AES-128, AES-192, or AES-256. +-func GenerateCryptoKey(s int) ([]byte, error) { +- var size int +- if s >= aesKeyLenLowerBound && s <= aesKeyLenUpperBound { +- size = s +- } else { +- size = aesKeyLenLowerBound +- } +- key := make([]byte, size) +- if _, err := io.ReadFull(rand.Reader, key); err != nil { +- return nil, errGenCryptoKey +- } +- +- return key, nil +-} +- +-// PBKDF2 is key derivation function to generate one way hash data +-// if used with AES, the keyLen can only be 16, 24, 32 +-// which stands for AES-128, AES-192 or AES-256 +-// iteration is pre-set to 409600 and salt is generated by random key generator +-func PBKDF2(password []byte, keyLen int, h func() hash.Hash) (string, error) { +- if len(password) == 0 { +- return "", errors.New("encrypt empty string failed") +- } +- salt, err := GenerateCryptoKey(CryptoKeyLen) +- if err != nil { +- return "", err +- } +- +- df := pbkdf2.Key(password, salt, iteration, keyLen, h) +- +- return hex.EncodeToString(df), nil +-} +- +-// EncryptAES encrypts plain text with AES encrypt algorithm(CFB) +-func EncryptAES(data string, aeskey string) (string, error) { +- plainText := []byte(data) +- key, err := hex.DecodeString(aeskey) +- if err != nil { +- return "", err +- } +- +- block, err := aes.NewCipher(key) +- if err != nil { +- return "", err +- } +- +- iv, err := GenerateCryptoKey(block.BlockSize()) +- if err != nil { +- return "", errors.Errorf("generate rand data for iv failed: %v", err) +- } +- mode := cipher.NewCFBEncrypter(block, iv) +- encryptData := make([]byte, len(plainText)) +- mode.XORKeyStream(encryptData, plainText) +- encryptData = append(iv, encryptData...) +- +- return hex.EncodeToString(encryptData), nil +-} +- +-// DecryptAES decrypts text with AES decrypt algorithm(CFB) +-func DecryptAES(data string, aeskey string) (string, error) { +- key, err := hex.DecodeString(aeskey) +- if err != nil { +- return "", err +- } +- +- cipherText, err := hex.DecodeString(data) +- if err != nil { +- return "", err +- } +- +- block, err := aes.NewCipher(key) +- if err != nil { +- return "", err +- } +- +- if len(cipherText) <= block.BlockSize() { +- return "", errors.Errorf("invalid cipher text length %v, it must larger than %v", len(cipherText), block.BlockSize()) +- } +- +- decrypter := cipher.NewCFBDecrypter(block, cipherText[:block.BlockSize()]) +- decryptData := make([]byte, len(cipherText)-block.BlockSize()) +- decrypter.XORKeyStream(decryptData, cipherText[block.BlockSize():]) +- +- return string(decryptData), nil +-} +- + // GenerateRSAKey generates a RAS key pair with key size s + // the recommend key size is 4096 and which will be use when + // key size is less than it +diff --git a/util/cipher_test.go b/util/cipher_test.go +index 4bbe894b..834c297c 100644 +--- a/util/cipher_test.go ++++ b/util/cipher_test.go +@@ -40,81 +40,6 @@ const ( + maxRepeatTime = 1000000 + ) + +-func TestAES(t *testing.T) { +- var testcases = []struct { +- name string +- length int +- wantErr bool +- text string +- hash func() hash.Hash +- }{ +- { +- name: "TC1 - normal case with key length 16", +- length: 16, +- text: "abcdefghijklmnopqrstuvwxyz", +- hash: sha256.New, +- wantErr: false, +- }, +- { +- name: "TC2 - normal case with key length 24", +- length: 24, +- text: "1234567890", +- hash: sha256.New, +- wantErr: false, +- }, +- { +- name: "TC3 - normal case with key length 32", +- length: 32, +- text: "!@#$%^&*()_+:>