diff --git a/internal/feature_install.go b/internal/feature_install.go index 50af4dc..4cac428 100644 --- a/internal/feature_install.go +++ b/internal/feature_install.go @@ -126,7 +126,6 @@ func (f *ScriptBasedSoftwareUpdatable) installModule( setLastOS(su, newOS(cid, module, hawkbit.StatusStarted)) storage.WriteLn(s, string(hawkbit.StatusStarted)) Started: - // Downloading logger.Debugf("[%s.%s] Downloading module", module.Name, module.Version) setLastOS(su, newOS(cid, module, hawkbit.StatusDownloading)) diff --git a/internal/storage/download.go b/internal/storage/download.go index dcb1636..e279e54 100644 --- a/internal/storage/download.go +++ b/internal/storage/download.go @@ -23,7 +23,6 @@ import ( "fmt" "hash" "io" - "io/ioutil" "net/http" "net/url" "os" @@ -39,8 +38,7 @@ const prefix = "_temporary-" var secureCiphers = supportedCipherSuites() // downloadArtifact tries to resume previous download operation or perform a new download. -func downloadArtifact(to string, artifact *Artifact, progress progressBytes, serverCert string, retryCount int, retryInterval time.Duration, - done chan struct{}) error { +func downloadArtifact(to string, artifact *Artifact, progress progressBytes, serverCert string, retryCount int, retryInterval time.Duration, postProcess func(fileName string) error, done chan struct{}) error { logger.Infof("download [%s] to file [%s]", artifact.Link, to) // Check for available file. @@ -95,6 +93,12 @@ func downloadArtifact(to string, artifact *Artifact, progress progressBytes, ser } } + if postProcess != nil { + if err := postProcess(tmp); err != nil { + return err + } + } + // Rename to the original file name. return os.Rename(tmp, to) } @@ -143,7 +147,7 @@ func resume(to string, offset int64, artifact *Artifact, progress progressBytes, func downloadFile(file *os.File, input io.ReadCloser, to string, offset int64, artifact *Artifact, progress progressBytes, serverCert string, retryCount int, retryInterval time.Duration, done chan struct{}) (int64, error) { - w, err := copy(file, input, int64(artifact.Size)-offset, progress, done) + w, err := copyWithProgress(file, input, int64(artifact.Size)-offset, progress, done) if err == nil { err = validate(to, artifact.HashType, artifact.HashValue) offset = 0 // in case of error, re-download the file @@ -237,7 +241,7 @@ func requestDownload(link string, offset int64, serverCert string) (*http.Respon var transport http.Transport var caCertPool *x509.CertPool if len(serverCert) > 0 { - caCert, err := ioutil.ReadFile(serverCert) + caCert, err := os.ReadFile(serverCert) if err != nil { return nil, fmt.Errorf("error reading CA certificate file - \"%s\"", serverCert) } @@ -275,7 +279,7 @@ func download(to string, in io.ReadCloser, artifact *Artifact, progress progress return downloadFile(file, in, to, 0, artifact, progress, serverCert, retryCount, retryInterval, done) } -func copy(dst io.Writer, src io.Reader, size int64, progress progressBytes, done chan struct{}) (w int64, err error) { +func copyWithProgress(dst io.Writer, src io.Reader, size int64, progress progressBytes, done chan struct{}) (w int64, err error) { buf := make([]byte, 32*1024) for { select { diff --git a/internal/storage/download_test.go b/internal/storage/download_test.go index dd6bfd7..91324a0 100644 --- a/internal/storage/download_test.go +++ b/internal/storage/download_test.go @@ -119,22 +119,22 @@ func TestDownloadToFile(t *testing.T) { HashType: "SHA256", HashValue: "4eefb9a7a40a8b314b586a00f307157043c0bbe4f59fa39cba88773680758bc3", }, - }, "", "", t) + }, "", t) } // TestDownloadToFileSecureSystemPool tests downloadToFile function, using secure protocol(s) and certificates from system pool. func TestDownloadToFileSecureSystemPool(t *testing.T) { setSSLCerts(t) defer unsetSSLCerts(t) - testDownloadToFileSecure("", "", t) + testDownloadToFileSecure("", t) } // TestDownloadToFileSecureCustomCertificate tests downloadToFile function, using secure protocol(s) and a custom certificate. func TestDownloadToFileSecureCustomCertificate(t *testing.T) { - testDownloadToFileSecure(validCert, validKey, t) + testDownloadToFileSecure(validCert, t) } -func testDownloadToFileSecure(certFile, certKey string, t *testing.T) { +func testDownloadToFileSecure(certFile string, t *testing.T) { testDownloadToFile([]*Artifact{ { // An Artifact with MD5 checksum. FileName: "test.txt", Size: 65536, Link: "https://localhost:43234/test.txt", @@ -151,10 +151,10 @@ func testDownloadToFileSecure(certFile, certKey string, t *testing.T) { HashType: "SHA256", HashValue: "4eefb9a7a40a8b314b586a00f307157043c0bbe4f59fa39cba88773680758bc3", }, - }, certFile, certKey, t) + }, certFile, t) } -func testDownloadToFile(arts []*Artifact, certFile, certKey string, t *testing.T) { +func testDownloadToFile(arts []*Artifact, certFile string, t *testing.T) { for _, art := range arts { t.Run(art.HashType, func(t *testing.T) { // Prepare @@ -174,7 +174,7 @@ func testDownloadToFile(arts []*Artifact, certFile, certKey string, t *testing.T // 1. Resume download of corrupted temporary file. WriteLn(filepath.Join(dir, prefix+art.FileName), "wrong start") - if err := downloadArtifact(name, art, nil, certFile, 0, 0, make(chan struct{})); err == nil { + if err := downloadArtifact(name, art, nil, certFile, 0, 0, nil, make(chan struct{})); err == nil { t.Fatal("download of corrupted temporary file must fail") } @@ -183,7 +183,7 @@ func testDownloadToFile(arts []*Artifact, certFile, certKey string, t *testing.T callback := func(bytes int64) { close(done) } - if err := downloadArtifact(name, art, callback, certFile, 0, 0, done); err != ErrCancel { + if err := downloadArtifact(name, art, callback, certFile, 0, 0, nil, done); err != ErrCancel { t.Fatalf("failed to cancel download operation: %v", err) } if _, err := os.Stat(filepath.Join(dir, prefix+art.FileName)); os.IsNotExist(err) { @@ -192,13 +192,13 @@ func testDownloadToFile(arts []*Artifact, certFile, certKey string, t *testing.T // 3. Resume previous download operation. callback = func(bytes int64) { /* Do nothing. */ } - if err := downloadArtifact(name, art, callback, certFile, 0, 0, make(chan struct{})); err != nil { + if err := downloadArtifact(name, art, callback, certFile, 0, 0, nil, make(chan struct{})); err != nil { t.Fatalf("failed to download artifact: %v", err) } check(name, art.Size, t) // 4. Download available file. - if err := downloadArtifact(name, art, callback, certFile, 0, 0, make(chan struct{})); err != nil { + if err := downloadArtifact(name, art, callback, certFile, 0, 0, nil, make(chan struct{})); err != nil { t.Fatalf("failed to download artifact: %v", err) } check(name, art.Size, t) @@ -211,14 +211,14 @@ func testDownloadToFile(arts []*Artifact, certFile, certKey string, t *testing.T // 5. Try to resume with file bigger than expected. WriteLn(filepath.Join(dir, prefix+art.FileName), "1111111111111") art.Size -= 10 - if err := downloadArtifact(name, art, nil, certFile, 0, 0, make(chan struct{})); err == nil { + if err := downloadArtifact(name, art, nil, certFile, 0, 0, nil, make(chan struct{})); err == nil { t.Fatal("validate resume with file bigger than expected") } // 6. Try to resume from missing link. WriteLn(filepath.Join(dir, prefix+art.FileName), "1111111111111") art.Link = "http://localhost:43234/test-missing.txt" - if err := downloadArtifact(name, art, nil, "", 0, 0, make(chan struct{})); err == nil { + if err := downloadArtifact(name, art, nil, "", 0, 0, nil, make(chan struct{})); err == nil { t.Fatal("failed to validate with missing link") } @@ -256,7 +256,7 @@ func TestDownloadToFileLocalLink(t *testing.T) { HashValue: "4eefb9a7a40a8b314b586a00f307157043c0bbe4f59fa39cba88773680758bc3", Local: true, }, - }, "", "", t) + }, "", t) } // TestDownloadToFileError tests downloadToFile function for some edge cases. @@ -284,33 +284,33 @@ func TestDownloadToFileError(t *testing.T) { // 1. Resume is not supported. WriteLn(filepath.Join(dir, prefix+art.FileName), "1111") - if err := downloadArtifact(name, art, nil, "", 0, 0, make(chan struct{})); err != nil { + if err := downloadArtifact(name, art, nil, "", 0, 0, nil, make(chan struct{})); err != nil { t.Fatalf("failed to download file artifact: %v", err) } check(name, art.Size, t) // 2. Try with missing checksum. art.HashValue = "" - if err := downloadArtifact(name, art, nil, "", 0, 0, make(chan struct{})); err == nil { + if err := downloadArtifact(name, art, nil, "", 0, 0, nil, make(chan struct{})); err == nil { t.Fatal("validated with missing checksum") } // 3. Try with missing link. art.Link = "http://localhost:43234/test-missing.txt" - if err := downloadArtifact(name, art, nil, "", 0, 0, make(chan struct{})); err == nil { + if err := downloadArtifact(name, art, nil, "", 0, 0, nil, make(chan struct{})); err == nil { t.Fatal("failed to validate with missing link") } // 4. Try with wrong checksum type. art.Link = "http://localhost:43234/test-simple.txt" art.HashType = "" - if err := downloadArtifact(name, art, nil, "", 0, 0, make(chan struct{})); err == nil { + if err := downloadArtifact(name, art, nil, "", 0, 0, nil, make(chan struct{})); err == nil { t.Fatal("validate with wrong checksum type") } // 5. Try with wrong checksum format. art.HashValue = ";;" - if err := downloadArtifact(name, art, nil, "", 0, 0, make(chan struct{})); err == nil { + if err := downloadArtifact(name, art, nil, "", 0, 0, nil, make(chan struct{})); err == nil { t.Fatal("validate with wrong checksum format") } @@ -318,7 +318,7 @@ func TestDownloadToFileError(t *testing.T) { art.HashType = "MD5" art.HashValue = "ab2ce340d36bbaafe17965a3a2c6ed5b" art.Size -= 10 - if err := downloadArtifact(name, art, nil, "", 0, 0, make(chan struct{})); err == nil { + if err := downloadArtifact(name, art, nil, "", 0, 0, nil, make(chan struct{})); err == nil { t.Fatal("validate with file bigger than expected") } @@ -346,11 +346,11 @@ func TestRobustDownloadRetryBadStatus(t *testing.T) { name := filepath.Join(dir, art.FileName) - if err := downloadArtifact(name, art, nil, "", 1, 0, make(chan struct{})); err == nil { + if err := downloadArtifact(name, art, nil, "", 1, 0, nil, make(chan struct{})); err == nil { t.Fatal("error is expected when downloading artifact, due to bad response status") } - if err := downloadArtifact(name, art, nil, "", 5, time.Second, make(chan struct{})); err != nil { + if err := downloadArtifact(name, art, nil, "", 5, time.Second, nil, make(chan struct{})); err != nil { t.Fatal("expected to handle download error, by using retry download strategy") } check(name, art.Size, t) @@ -359,7 +359,7 @@ func TestRobustDownloadRetryBadStatus(t *testing.T) { t.Fatalf("failed to delete test file %s", name) } setIncorrectBehavior(2, false, false) - if err := downloadArtifact(name, art, nil, "", 0, 0, make(chan struct{})); err == nil { + if err := downloadArtifact(name, art, nil, "", 0, 0, nil, make(chan struct{})); err == nil { t.Fatal("error is expected when downloading artifact, due to bad response status") } } @@ -417,7 +417,7 @@ func testCopyError(withInsufficientRetryCount bool, withCorruptedFile bool, t *t if withInsufficientRetryCount { retryCount = 2 } - err := downloadArtifact(name, art, nil, "", retryCount, 2*time.Second, make(chan struct{})) + err := downloadArtifact(name, art, nil, "", retryCount, 2*time.Second, nil, make(chan struct{})) if withInsufficientRetryCount { if err == nil { t.Fatal("error is expected when downloading artifact, due to copy error") @@ -461,22 +461,22 @@ func TestDownloadToFileSecureError(t *testing.T) { // 1. Server uses expired certificate art.Link = "https://localhost:43234/test.txt" - if err := downloadArtifact(name, art, nil, "", 0, 0, make(chan struct{})); err == nil { + if err := downloadArtifact(name, art, nil, "", 0, 0, nil, make(chan struct{})); err == nil { t.Fatalf("download must fail(client uses no certificate, server uses expired): %v", err) } - if err := downloadArtifact(name, art, nil, expiredCert, 0, 0, make(chan struct{})); err == nil { + if err := downloadArtifact(name, art, nil, expiredCert, 0, 0, nil, make(chan struct{})); err == nil { t.Fatalf("download must fail(client and server use expired certificate): %v", err) } // 2. Server uses untrusted certificate art.Link = "https://localhost:43235/test.txt" - if err := downloadArtifact(name, art, nil, "", 0, 0, make(chan struct{})); err == nil { + if err := downloadArtifact(name, art, nil, "", 0, 0, nil, make(chan struct{})); err == nil { t.Fatalf("download must fail(client uses no certificate, server uses untrusted): %v", err) } // 3. Server uses valid certificate art.Link = "https://localhost:43236/test.txt" - if err := downloadArtifact(name, art, nil, untrustedCert, 0, 0, make(chan struct{})); err == nil { + if err := downloadArtifact(name, art, nil, untrustedCert, 0, 0, nil, make(chan struct{})); err == nil { t.Fatalf("download must fail(client uses untrusted certificate, server uses valid): %v", err) } } diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 06f7bfe..b48903e 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -13,6 +13,8 @@ package storage import ( + "crypto/aes" + "crypto/cipher" "errors" "fmt" "io/fs" @@ -21,6 +23,7 @@ import ( "path/filepath" "sort" "strconv" + "strings" "time" "github.com/eclipse-kanto/software-update/hawkbit" @@ -268,11 +271,49 @@ func (st *Storage) DownloadModule(toDir string, module *Module, progress Progres continue } onlyLocalNoCopyArtifacts = false - if err = downloadArtifact(filepath.Join(toDir, sa.FileName), sa, callback, serverCert, retryCount, - retryInterval, st.done); err != nil { + var postProcess func(fileName string) error + if module.Metadata != nil && module.Metadata["AES256.key"] != "" { + var iv string + if iv = module.Metadata["AES256.iv"]; iv == "" { + return errors.New("AES256 key is provided, but initialization vector is missing. Only CBC encryption is supported") + } + postProcess = func(fileName string) error { + data, ppError := os.ReadFile(fileName) + if ppError != nil { + return ppError + } + format := module.Metadata["AES256.format"] + cipherTextDecoded, ppError := decodeString(format, strings.TrimSpace(string(data))) + if ppError != nil { + return fmt.Errorf("unable to decode artifact: %s", ppError) + } + encKeyDecoded, ppError := decodeString(format, module.Metadata["AES256.key"]) + if ppError != nil { + return fmt.Errorf("unable to decode the provided key: %s", ppError) + } + ivDecoded, ppError := decodeString(format, iv) + if ppError != nil { + return fmt.Errorf("unable to decode the initialization vector (IV): %s", ppError) + } + block, ppError := aes.NewCipher([]byte(encKeyDecoded)) + if ppError != nil { + return ppError + } + cipher.NewCBCDecrypter(block, []byte(ivDecoded)).CryptBlocks([]byte(cipherTextDecoded), []byte(cipherTextDecoded)) + return os.WriteFile(fileName, cipherTextDecoded, 0755) + + } + } + defer func() { + if r := recover(); r != nil { // the cipher.NewCBCDecrypter panics against all good practices... + err = fmt.Errorf("error during decryption %v", r) + } + }() + if err = downloadArtifact(filepath.Join(toDir, sa.FileName), sa, callback, serverCert, retryCount, retryInterval, postProcess, st.done); err != nil { return err } } + if progress != nil && onlyLocalNoCopyArtifacts { progress(100) } diff --git a/internal/storage/storage_test.go b/internal/storage/storage_test.go index 48975b0..3953f39 100644 --- a/internal/storage/storage_test.go +++ b/internal/storage/storage_test.go @@ -18,14 +18,19 @@ import ( "archive/zip" "bytes" "context" + "crypto/aes" + "crypto/cipher" "crypto/md5" + "encoding/base64" "encoding/hex" + "errors" "fmt" "net/http" "os" "path/filepath" "reflect" "strconv" + "strings" "testing" "time" @@ -163,7 +168,7 @@ func TestLoadSoftwareUpdatables(t *testing.T) { defer os.RemoveAll(dir) // Host dependency descriptions as module.zip - srv, art, _ := ddHost("module1.zip", t) + srv, art, _ := ddHost("module1.zip", t, nil) defer srv.close() // Create Storage object @@ -224,7 +229,7 @@ func TestDownloadArchiveModule(t *testing.T) { defer os.RemoveAll(dir) // Host dependency descriptions as module.zip - srv, art, dds := ddHost("module2.zip", t) + srv, art, dds := ddHost("module2.zip", t, nil) defer srv.close() // Create Storage object @@ -287,6 +292,192 @@ func TestDownloadArchiveModule(t *testing.T) { } } +// TestDownloadAESEncryptedModuleBase64 tests DownloadModule with AES encrypted artifacts. +func TestDownloadAESEncryptedModuleBase64(t *testing.T) { + testDownloadAESEncryptedModule(t, "base64") +} + +// TestDownloadAESEncryptedModuleBase64Raw tests DownloadModule with AES encrypted artifacts. +func TestDownloadAESEncryptedModuleBase64Raw(t *testing.T) { + testDownloadAESEncryptedModule(t, "base64raw") +} + +// TestDownloadAESEncryptedModuleHex tests DownloadModule with AES encrypted artifacts. +func TestDownloadAESEncryptedModuleHex(t *testing.T) { + testDownloadAESEncryptedModule(t, "hex") +} + +func testDownloadAESEncryptedModule(t *testing.T, format string) { + dir := "_tmp-storage" + if err := os.MkdirAll(dir, 0755); err != nil { + t.Fatalf("fail create temporary directory: %v", err) + } + + defer os.RemoveAll(dir) + + key := "my32digitkey12345678901234567890" + iv := "my16digitIvKey12" + + srv, art, _ := ddHost(fmt.Sprintf("module-%s.zip", format), t, func(data []byte) ([]byte, error) { + block, err := aes.NewCipher([]byte(key)) + if err != nil { + return nil, err + } + + var b []byte + length := len(data) + if length%16 != 0 { + extendBlock := 16 - (length % 16) + b = make([]byte, length+extendBlock) + copy(b[length:], bytes.Repeat([]byte{uint8(extendBlock)}, extendBlock)) + } else { + b = make([]byte, length) + } + copy(b, data) + + cipherText := make([]byte, len(b)) + cipher.NewCBCEncrypter(block, []byte(iv)).CryptBlocks(cipherText, b) + + return []byte(encode(format, string(cipherText), t)), nil + }) + defer srv.close() + + store, err := NewStorage(dir) + if err != nil { + t.Fatalf("fail to initialize local storage: %v", err) + } + defer store.Close() + + path := filepath.Join(store.DownloadPath, "0", "0") + + for _, modules := range []struct { + name string + m *Module + ee map[string]error + }{ + { + "Successful Decryption", + &Module{ + Name: "name1", Version: "1", + Artifacts: []*Artifact{art}, + Metadata: map[string]string{ + "AES256.key": encode(format, key, t), + "AES256.iv": encode(format, iv, t), + "AES256.format": format, + }, + }, + nil, + }, + { + "IV Missing", + &Module{ + Name: "name2", Version: "2", + Artifacts: []*Artifact{art}, + Metadata: map[string]string{ + "AES256.key": encode(format, key, t), + "AES256.format": format, + }, + }, + map[string]error{ + "base64": errors.New("AES256 key is provided, but initialization vector is missing. Only CBC encryption is supported"), + "hex": errors.New("AES256 key is provided, but initialization vector is missing. Only CBC encryption is supported"), + }, + }, + { + "Malformed AES Key", + &Module{ + Name: "name3", Version: "3", + Artifacts: []*Artifact{art}, + Metadata: map[string]string{ + "AES256.key": "#$#$#$#$#$#$", + "AES256.iv": encode(format, iv, t), + "AES256.format": format, + }, + }, + map[string]error{ + "base64": errors.New("unable to decode the provided key: illegal base64 data at input byte 0"), + "hex": errors.New("unable to decode the provided key: encoding/hex: invalid byte: U+0023 '#'"), + }, + }, + { + "Malformed AES IV", + &Module{ + Name: "name4", Version: "4", + Artifacts: []*Artifact{art}, + Metadata: map[string]string{ + "AES256.key": encode(format, key, t), + "AES256.iv": "#$#$#$#$#$#$", + "AES256.format": format, + }, + }, + map[string]error{ + "base64": errors.New("unable to decode the initialization vector (IV): illegal base64 data at input byte 0"), + "hex": errors.New("unable to decode the initialization vector (IV): encoding/hex: invalid byte: U+0023 '#'"), + }, + }, + { + "Invalid AES Key", + &Module{ + Name: "name5", Version: "5", + Artifacts: []*Artifact{art}, + Metadata: map[string]string{ + "AES256.key": encode(format, ("key"), t), + "AES256.iv": encode(format, iv, t), + "AES256.format": format, + }, + }, + map[string]error{ + "base64": errors.New("crypto/aes: invalid key size 3"), + "hex": errors.New("crypto/aes: invalid key size 3"), + }, + }, + { + "Invalid AES IV", + &Module{ + Name: "name6", Version: "6", + Artifacts: []*Artifact{art}, + Metadata: map[string]string{ + "AES256.key": encode(format, key, t), + "AES256.iv": encode(format, ("iv"), t), + "AES256.format": format, + }, + }, + map[string]error{ + "base64": errors.New("error during decryption cipher.NewCBCDecrypter: IV length must equal block size"), + "hex": errors.New("error during decryption cipher.NewCBCDecrypter: IV length must equal block size"), + }, + }, + } { + t.Run(modules.name, func(t *testing.T) { + if err := store.DownloadModule(path, modules.m, nil, "", 0, 0, nil); err != nil { + f, _ := strings.CutSuffix(format, "raw") + if modules.ee != nil && err.Error() == modules.ee[f].Error() { + return + } + t.Fatalf("fail to download module [Hash: %s, File: %s]: %v", art.HashValue, hex.EncodeToString(srv.data), err) + } + existence(filepath.Join(path, art.FileName), true, "[initial download]", t) + + if err := ExtractArchive(path); err != nil { + t.Fatalf("fail to extract module [%s]: %v", path, err) + } + }) + } +} + +func encode(format, data string, t *testing.T) string { + switch format { + case "base64": + return base64.StdEncoding.EncodeToString([]byte(data)) + case "base64raw": + return base64.RawStdEncoding.EncodeToString([]byte(data)) + case "hex": + return hex.EncodeToString([]byte(data)) + } + t.Errorf("unsupported encoding format %s", format) + return "" +} + func existence(path string, exists bool, prefix string, t *testing.T) { if _, err := os.Stat(path); os.IsNotExist(err) == exists { t.Fatalf("%s fail to check file existence [path: %s, isNotExists: %v]", prefix, path, exists) @@ -314,7 +505,7 @@ func hm(art *Artifact) []*hawkbit.SoftwareModuleAction { } // ddHost create and start a HTTP server on port: 43234 -func ddHost(name string, t *testing.T) (*sWeb, *Artifact, map[string]*hawkbit.DependencyDescription) { +func ddHost(name string, t *testing.T, encrypt func(data []byte) ([]byte, error)) (*sWeb, *Artifact, map[string]*hawkbit.DependencyDescription) { w := &sWeb{name: name, t: t} // Create dependency descriptions @@ -340,6 +531,13 @@ func ddHost(name string, t *testing.T) (*sWeb, *Artifact, map[string]*hawkbit.De } a.Close() w.data = buf.Bytes() + if encrypt != nil { + var err error + w.data, err = encrypt(w.data) + if err != nil { + w.t.Fatalf("fail to encrypt data %v", err) + } + } // Create new HTTP server. w.srv = &http.Server{Addr: ":43234"} diff --git a/internal/storage/utils.go b/internal/storage/utils.go index d053d14..d972cf9 100644 --- a/internal/storage/utils.go +++ b/internal/storage/utils.go @@ -14,6 +14,8 @@ package storage import ( "bufio" + "encoding/base64" + "encoding/hex" "encoding/json" "fmt" "io/ioutil" @@ -319,3 +321,14 @@ func contains(s []string, str string) bool { } return false } + +func decodeString(format string, s string) ([]byte, error) { + if strings.ToLower(format) == "hex" { + return hex.DecodeString(s) + } + data, err := base64.StdEncoding.DecodeString(s) + if err != nil { + return base64.RawStdEncoding.DecodeString(s) + } + return data, nil +}