diff --git a/go.mod b/go.mod index 6dcc7e4..4bb77e4 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.25.0 require ( github.com/BurntSushi/toml v1.6.0 - github.com/cloudbase/garm-provider-common v0.1.7 + github.com/cloudbase/garm-provider-common v0.1.9 github.com/linode/linodego v1.68.0 github.com/stretchr/testify v1.11.1 golang.org/x/oauth2 v0.36.0 @@ -18,7 +18,7 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/minio/sio v0.4.1 // indirect + github.com/minio/sio v0.4.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569 // indirect diff --git a/go.sum b/go.sum index d1e1f97..408b880 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/cloudbase/garm-provider-common v0.1.7 h1:V0upTejFRDiyFBO4hhkMWmPtmRTguyOt/4i1u9/rfbg= -github.com/cloudbase/garm-provider-common v0.1.7/go.mod h1:2O51WbcfqRx5fDHyyJgIFq7KdTZZnefsM+aoOchyleU= +github.com/cloudbase/garm-provider-common v0.1.9 h1:ZL53ma/j7BgMAqW/OJ/jCnx1MUd8hLcYxUTEFb/o/e0= +github.com/cloudbase/garm-provider-common v0.1.9/go.mod h1:8tnJcLXtaMUDEUgX3MGLFEYnMpiCAKaKWPtzNnzEpAE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -24,8 +24,8 @@ github.com/linode/linodego v1.68.0 h1:lAsXuHm/cwQT3KCbVpMGtRiH8IpQl4hUuBOXpqkuNw github.com/linode/linodego v1.68.0/go.mod h1:X7nmTNq1GmZT4bG6w9aiuVrOnhVxYaywrzxM+buC/qU= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/minio/sio v0.4.1 h1:EMe3YBC1nf+sRQia65Rutxi+Z554XPV0dt8BIBA+a/0= -github.com/minio/sio v0.4.1/go.mod h1:oBSjJeGbBdRMZZwna07sX9EFzZy+ywu5aofRiV1g79I= +github.com/minio/sio v0.4.3 h1:JqyID1XM86KwBZox5RAdLD4MLPIDoCY2cke2CXCJCkg= +github.com/minio/sio v0.4.3/go.mod h1:4ANoe4CCXqnt1FCiLM0+vlBUhhWZzVOhYCz0069KtFc= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/vendor/github.com/cloudbase/garm-provider-common/cloudconfig/templates.go b/vendor/github.com/cloudbase/garm-provider-common/cloudconfig/templates.go index 6193f82..8c3b5d7 100644 --- a/vendor/github.com/cloudbase/garm-provider-common/cloudconfig/templates.go +++ b/vendor/github.com/cloudbase/garm-provider-common/cloudconfig/templates.go @@ -96,7 +96,16 @@ if [ ! -d "$RUN_HOME" ];then downloadAndExtractRunner sendStatus "installing dependencies" cd "$RUN_HOME" - sudo ./bin/installdependencies.sh || fail "failed to install dependencies" + attempt=1 + while true; do + sudo ./bin/installdependencies.sh && break + if [ $attempt -gt 5 ];then + fail "failed to install dependencies after $attempt attempts" + fi + sendStatus "failed to install dependencies (attempt $attempt): (retrying in 15 seconds)" + attempt=$((attempt+1)) + sleep 15 + done else sendStatus "using cached runner found in $RUN_HOME" cd "$RUN_HOME" diff --git a/vendor/github.com/cloudbase/garm-provider-common/util/util.go b/vendor/github.com/cloudbase/garm-provider-common/util/util.go index 36ce09b..2dd879e 100644 --- a/vendor/github.com/cloudbase/garm-provider-common/util/util.go +++ b/vendor/github.com/cloudbase/garm-provider-common/util/util.go @@ -18,8 +18,10 @@ import ( "bytes" "compress/gzip" "crypto/rand" + "crypto/x509" "encoding/base64" "encoding/binary" + "encoding/pem" "fmt" "io" "math/big" @@ -201,22 +203,22 @@ func GetTools(osType params.OSType, osArch params.OSArch, tools []params.RunnerA return params.RunnerApplicationDownload{}, fmt.Errorf("unsupported OS arch: %s", osArch) } + ghArch, err := ResolveToGithubArch(string(osArch)) + if err != nil { + return params.RunnerApplicationDownload{}, fmt.Errorf("failed to convert osArch: %w", err) + } + ghOS, err := ResolveToGithubOSType(string(osType)) + if err != nil { + return params.RunnerApplicationDownload{}, fmt.Errorf("failed to convert osType: %w", err) + } + // Find tools for OS/Arch. for _, tool := range tools { if tool.GetOS() == "" || tool.GetArchitecture() == "" { continue } - ghArch, err := ResolveToGithubArch(string(osArch)) - if err != nil { - continue - } - - ghOS, err := ResolveToGithubOSType(string(osType)) - if err != nil { - continue - } - if tool.GetArchitecture() == ghArch && tool.GetOS() == ghOS { + if (tool.GetArchitecture() == ghArch || tool.GetArchitecture() == string(osArch)) && (tool.GetOS() == ghOS || tool.GetOS() == string(osType)) { return tool, nil } } @@ -325,3 +327,33 @@ func CompressData(data []byte) ([]byte, error) { return b.Bytes(), nil } + +// SanitizeCABundle parses a PEM bundle, validates that each block is a +// valid certificate, and returns a new bundle with duplicates removed, +// preserving the original order. +func SanitizeCABundle(bundle []byte) ([]byte, error) { + seen := map[string]struct{}{} + var buf bytes.Buffer + + rest := bundle + for { + var block *pem.Block + block, rest = pem.Decode(rest) + if block == nil { + break + } + if _, err := x509.ParseCertificates(block.Bytes); err != nil { + return nil, fmt.Errorf("invalid certificate in PEM bundle: %w", err) + } + key := string(block.Bytes) + if _, exists := seen[key]; exists { + continue + } + if err := pem.Encode(&buf, block); err != nil { + return nil, fmt.Errorf("failed to encode PEM block: %w", err) + } + seen[key] = struct{}{} + } + + return buf.Bytes(), nil +} diff --git a/vendor/github.com/minio/sio/.golangci.yml b/vendor/github.com/minio/sio/.golangci.yml index dd2cc77..c690c8b 100644 --- a/vendor/github.com/minio/sio/.golangci.yml +++ b/vendor/github.com/minio/sio/.golangci.yml @@ -1,34 +1,47 @@ -linters-settings: - golint: - min-confidence: 0 - - misspell: - locale: US - - staticcheck: - checks: ['all', '-SA6002'] - +version: "2" linters: - disable-all: true + default: none enable: - durationcheck + - errcheck - gocritic - - gofumpt - - goimports - gomodguard + - gosec - govet - ineffassign - misspell - revive - staticcheck - - tenv - - typecheck - unconvert - unused - -issues: - exclude-use-default: false - exclude: - - should have a package comment - - error strings should not be capitalized or end with punctuation or a newline - - don't use ALL_CAPS in Go names + settings: + misspell: + locale: US + revive: + rules: + - name: package-comments + disabled: true + - name: error-strings + disabled: true + staticcheck: + checks: + - all + - -SA6002 + - -ST1003 + exclusions: + generated: lax + rules: + - linters: + - revive + text: don't use ALL_CAPS in Go names + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/vendor/github.com/minio/sio/.travis.yml b/vendor/github.com/minio/sio/.travis.yml deleted file mode 100644 index 3efbefb..0000000 --- a/vendor/github.com/minio/sio/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -go_import_path: github.com/minio/sio -sudo: required - -dist: trusty - -language: go - -os: -- linux - -env: -- ARCH=x86_64 -- ARCH=i686 - -go: -- "1.12" -- "1.13" - -script: -- diff -au <(gofmt -d .) <(printf "") -- go vet ./... -- go test -v ./... diff --git a/vendor/github.com/minio/sio/CONTRIBUTING.md b/vendor/github.com/minio/sio/CONTRIBUTING.md new file mode 100644 index 0000000..a17199a --- /dev/null +++ b/vendor/github.com/minio/sio/CONTRIBUTING.md @@ -0,0 +1,262 @@ +# Contributing to sio + +Thank you for your interest in contributing to `sio`! This document provides guidelines and instructions for contributing. + +## Code of Conduct + +Be respectful and professional in all interactions. We're here to build great software together. + +## Getting Started + +### Prerequisites + +- Go 1.24 or later +- Git +- golangci-lint (for linting) +- Basic understanding of cryptography (helpful but not required) + +### Development Setup + +1. Fork the repository on GitHub +2. Clone your fork: + ```bash + git clone https://github.com/YOUR_USERNAME/sio.git + cd sio + ``` +3. Add the upstream repository: + ```bash + git remote add upstream https://github.com/minio/sio.git + ``` +4. Install dependencies: + ```bash + go mod download + ``` + +## Development Workflow + +### 1. Create a Branch + +```bash +git checkout -b feature/your-feature-name +``` + +Use prefixes: + +- `feature/` - New features +- `fix/` - Bug fixes +- `docs/` - Documentation changes +- `perf/` - Performance improvements +- `refactor/` - Code refactoring + +### 2. Make Changes + +Follow the coding standards below and ensure your code: + +- Is well-tested +- Includes documentation +- Passes all existing tests +- Doesn't introduce security vulnerabilities + +### 3. Run Tests + +```bash +# Run all tests +go test -v ./... + +# Run with race detector +go test -race ./... + +# Run with coverage +go test -coverprofile=coverage.out ./... +go tool cover -html=coverage.out +``` + +### 4. Run Linters + +```bash +# Run gofmt +gofmt -s -w . + +# Run go vet +go vet ./... + +# Run golangci-lint +golangci-lint run +``` + +### 5. Commit Changes + +Write clear commit messages following this format: + +``` +Short summary (50 chars or less) + +More detailed explanation if needed. Wrap at 72 characters. +Explain the problem this commit solves and why you chose +this solution. + +Fixes #123 +``` + +### 6. Push and Create Pull Request + +```bash +git push origin feature/your-feature-name +``` + +Then create a pull request on GitHub with: + +- Clear description of the changes +- Reference to related issues +- Screenshots/examples if applicable + +## Coding Standards + +### Go Style + +- Follow [Effective Go](https://golang.org/doc/effective_go.html) +- Use `gofmt` for formatting +- Keep functions small and focused +- Write self-documenting code with clear names + +### Error Handling + +- Always check errors +- Wrap errors with context using `fmt.Errorf("context: %w", err)` +- Return errors, don't panic (except for truly unrecoverable situations) +- Use typed errors for API boundaries + +### Testing + +- Write table-driven tests where appropriate +- Test edge cases and error conditions +- Aim for >80% code coverage +- Use meaningful test names: `TestFunctionName_Scenario` + +### Documentation + +- Add godoc comments for all exported types, functions, and constants +- Include usage examples for complex functionality +- Update README.md if adding user-facing features +- Document security considerations + +### Security + +- Never commit secrets or sensitive data +- Be cautious with cryptographic code +- Consider timing attacks and side channels +- Add tests for security-critical code paths + +## Pull Request Process + +1. **Update documentation** - README.md, godoc comments, etc. +2. **Add tests** - New code must include tests +3. **Pass CI checks** - All tests and linters must pass +4. **Get reviewed** - At least one maintainer must approve +5. **Squash commits** - Keep history clean with meaningful commits + +### PR Checklist + +- [ ] Tests added/updated and passing +- [ ] Documentation updated +- [ ] golangci-lint passes +- [ ] No breaking changes (or documented in PR) +- [ ] Commit messages are clear +- [ ] Branch is up to date with master + +## Testing Guidelines + +### Unit Tests + +Focus on: + +- Individual function behavior +- Edge cases (empty inputs, max size, etc.) +- Error conditions +- Different cipher suites + +### Integration Tests + +Focus on: + +- End-to-end encryption/decryption +- Different stream sizes +- Reader/Writer interfaces +- Version compatibility + +### Fuzzing + +For cryptographic code, consider adding fuzz tests: + +```go +func FuzzDecrypt(f *testing.F) { + // Add corpus and fuzz implementation +} +``` + +## Benchmarking + +When making performance-related changes: + +```bash +# Run benchmarks +go test -bench=. -benchmem + +# Compare before/after +go test -bench=. -benchmem > old.txt +# make changes +go test -bench=. -benchmem > new.txt +benchstat old.txt new.txt +``` + +## Release Process + +(For maintainers) + +1. Update version numbers and CHANGELOG +2. Run full test suite including race detector +3. Tag release: `git tag v1.x.x` +4. Push tag: `git push origin v1.x.x` +5. GitHub Actions will create the release + +## Common Tasks + +### Adding a New Function + +1. Implement the function +2. Add godoc comment +3. Add unit tests +4. Add example test +5. Update README if user-facing + +### Fixing a Bug + +1. Add a test that reproduces the bug +2. Fix the bug +3. Verify the test now passes +4. Consider adding additional edge case tests + +### Improving Performance + +1. Add benchmark before changes +2. Make improvements +3. Run benchmark again +4. Include benchmark results in PR +5. Verify no functionality regression + +## Getting Help + +- **Questions**: Open a GitHub Discussion +- **Bugs**: Open a GitHub Issue +- **Security**: Email security@min.io +- **Chat**: Join MinIO Slack (link in README) + +## Recognition + +Contributors will be: + +- Listed in release notes +- Mentioned in commit history +- Added to CONTRIBUTORS file (if significant contribution) + +Thank you for contributing to sio! diff --git a/vendor/github.com/minio/sio/DARE.md b/vendor/github.com/minio/sio/DARE.md index 67c2ab2..fe89648 100644 --- a/vendor/github.com/minio/sio/DARE.md +++ b/vendor/github.com/minio/sio/DARE.md @@ -13,9 +13,10 @@ on (untrusted) storage providers. DARE specifies how to split an arbitrary data stream into small chunks (packages) and concatenate them into a tamper-proof chain. Tamper-proof means that an attacker is not able to: - - decrypt one or more packages. - - modify the content of one or more packages. - - reorder/rearrange one or more packages. + +- decrypt one or more packages. +- modify the content of one or more packages. +- reorder/rearrange one or more packages. An attacker is defined as somebody who has full access to the encrypted data but not to the encryption key. An attacker can also act as storage provider. @@ -23,46 +24,48 @@ but not to the encryption key. An attacker can also act as storage provider. ### 2.1 Cryptographic Notation DARE will use the following notations: - - The set **{a,b}** means select **one** of the provided values **a**, **b**. - - The concatenation of the byte sequences **a** and **b** is **a || b**. - - The function **len(seq)** returns the length of a byte sequence **seq** in bytes. - - The index access **seq[i]** accesses one byte at index **i** of the sequence **seq**. - - The range access **seq[i : j]** accesses a range of bytes starting at **i** (inclusive) - and ending at **j** (exclusive). - - The compare functions **a == b => f** and **a != b => f** succeed when **a** - is equal to **b** and **a** is not equal to **b** respectively and execute the command **f**. - - The function **CTC(a, b)** returns **1** only if **a** and **b** are equal, 0 otherwise. - CTC compares both values in **constant time**. - - **ENC(key, nonce, plaintext, addData)** represents the byte sequence which is - the output from an AEAD cipher authenticating the *addData*, encrypting and - authenticating the *plaintext* with the secret encryption *key* and the *nonce*. - - **DEC(key, nonce, ciphertext, addData)** represents the byte sequence which is - the output from an AEAD cipher verifying the integrity of the *ciphertext* & - *addData* and decrypting the *ciphertext* with the secret encryption *key* and - the *nonce*. The decryption **always** fails if the integrity check fails. + +- The set **{a,b}** means select **one** of the provided values **a**, **b**. +- The concatenation of the byte sequences **a** and **b** is **a || b**. +- The function **len(seq)** returns the length of a byte sequence **seq** in bytes. +- The index access **seq[i]** accesses one byte at index **i** of the sequence **seq**. +- The range access **seq[i : j]** accesses a range of bytes starting at **i** (inclusive) + and ending at **j** (exclusive). +- The compare functions **a == b => f** and **a != b => f** succeed when **a** + is equal to **b** and **a** is not equal to **b** respectively and execute the command **f**. +- The function **CTC(a, b)** returns **1** only if **a** and **b** are equal, 0 otherwise. + CTC compares both values in **constant time**. +- **ENC(key, nonce, plaintext, addData)** represents the byte sequence which is + the output from an AEAD cipher authenticating the _addData_, encrypting and + authenticating the _plaintext_ with the secret encryption _key_ and the _nonce_. +- **DEC(key, nonce, ciphertext, addData)** represents the byte sequence which is + the output from an AEAD cipher verifying the integrity of the _ciphertext_ & + _addData_ and decrypting the _ciphertext_ with the secret encryption _key_ and + the _nonce_. The decryption **always** fails if the integrity check fails. All numbers must be converted into byte sequences by using the little endian byte -order. An AEAD cipher will be either AES-256_GCM or CHACHA20_POLY1305. +order. An AEAD cipher will be either AES-256_GCM or CHACHA20_POLY1305. ## 2.2 Keys Both ciphers - AES-256_GCM and CHACHA20_POLY1305 - require a 32 byte key. The key **must** be unique for one encrypted data stream. Reusing a key **compromises** some security properties provided by DARE. See Appendix A for recommendations -about generating keys and preventing key reuse. +about generating keys and preventing key reuse. ## 2.3 Errors DARE defines the following errors: - - **err_unsupported_version**: Indicates that the header version is not supported. - - **err_unsupported_cipher**: Indicates that the cipher suite is not supported. - - **err_missing_header**: Indicates that the payload header is missing or incomplete. - - **err_payload_too_short**: Indicates that the actual payload size is smaller than the + +- **err_unsupported_version**: Indicates that the header version is not supported. +- **err_unsupported_cipher**: Indicates that the cipher suite is not supported. +- **err_missing_header**: Indicates that the payload header is missing or incomplete. +- **err_payload_too_short**: Indicates that the actual payload size is smaller than the payload size field of the header. - - **err_package_out_of_order**: Indicates that the sequence number of the package does - not match the expected sequence number. - - **err_tag_mismatch**: Indicates that the tag of the package does not match the tag - computed while decrypting the package. +- **err_package_out_of_order**: Indicates that the sequence number of the package does + not match the expected sequence number. +- **err_tag_mismatch**: Indicates that the tag of the package does not match the tag + computed while decrypting the package. ## 3. Package Format @@ -70,23 +73,23 @@ DARE splits an arbitrary data stream into a sequence of packages. Each package i encrypted separately. A package consists of a header, a payload and an authentication tag. -Header | Payload | Tag ----------|----------------|--------- -16 bytes | 1 byte - 64 KB | 16 bytes +| Header | Payload | Tag | +| -------- | -------------- | -------- | +| 16 bytes | 1 byte - 64 KB | 16 bytes | The header contains information about the package. It consists of: -Version | Cipher suite | Payload size | Sequence number | nonce ---------|--------------|------------------|------------------|--------- -1 byte | 1 byte | 2 bytes / uint16 | 4 bytes / uint32 | 8 bytes +| Version | Cipher suite | Payload size | Sequence number | nonce | +| ------- | ------------ | ---------------- | ---------------- | ------- | +| 1 byte | 1 byte | 2 bytes / uint16 | 4 bytes / uint32 | 8 bytes | -The first byte specifies the version of the format and is equal to 0x10 for DARE +The first byte specifies the version of the format and is equal to 0x10 for DARE version 1.0. The second byte specifies the cipher used to encrypt the package. -Cipher | Value -------------------|------- -AES-256_GCM | 0x00 -CHACHA20_POLY1305 | 0x01 +| Cipher | Value | +| ----------------- | ----- | +| AES-256_GCM | 0x00 | +| CHACHA20_POLY1305 | 0x01 | The payload size is an uint16 number. The real payload size is defined as the payload size field as uint32 + 1. This ensures that the payload can be exactly 64 KB long and @@ -94,7 +97,7 @@ prevents empty packages without a payload. The sequence number is an uint32 number identifying the package within a sequence of packages. It is a monotonically increasing number. The sequence number **must** be 0 for -the first package and **must** be incremented for every subsequent package. The +the first package and **must** be incremented for every subsequent package. The sequence number of the n-th package is n-1. This means a sequence of packages can consist of 2 ^ 32 packages and each package can hold up to 64 KB data. The maximum size of a data stream is limited by `64 KB * 2^32 = 256 TB`. This should be sufficient @@ -112,7 +115,7 @@ The payload contains the encrypted data. It must be at least 1 byte long and can The authentication tag is generated by the AEAD cipher while encrypting and authenticating the package. The authentication tag **must** always be verified while decrypting the package. -Decrypted content **must never** be returned before the authentication tag is successfully +Decrypted content **must never** be returned before the authentication tag is successfully verified. ## 4. Encryption @@ -120,7 +123,7 @@ verified. DARE encrypts every package separately. The header version, cipher suite and nonce **should** be the same for all encrypted packages of one data stream. It is **recommended** to not change this values within one sequence of packages. The nonce **should** be generated randomly once -at the beginning of the encryption process and repeated in every header. See Appendix B for +at the beginning of the encryption process and repeated in every header. See Appendix B for recommendations about generating random numbers. The sequence number is the sequence number of the previous package plus 1. The sequence number @@ -137,7 +140,7 @@ header[2:4] = little_endian( len(plaintext) - 1 ) header[4:8] = little_endian( sequence_number ) header[8:16] = nonce -payload || tag = ENC(key, header[4:16], plaintext, header[0:4]) +payload || tag = ENC(key, header[4:16], plaintext, header[0:4]) sequence_number = sequence_number + 1 ``` @@ -152,7 +155,7 @@ plaintext is returned. The decryption happens in three steps: to save the first expected sequence number at the beginning of the decryption process. After every successfully decrypted package this sequence number is incremented by 1. The sequence number of all packages **must** match the saved / expected number. -3. Verify that the authentication tag at the end of the package is equal to the authentication tag +3. Verify that the authentication tag at the end of the package is equal to the authentication tag computed while decrypting the package. This **must** happen in constant time. The decryption is defined as following: @@ -168,25 +171,27 @@ plaintext || tag := DEC(key, header[4:16], ciphertext, header[0:4]) CTC(ciphertext[len(plaintext) : len(plaintext) + 16], tag) != 1 => err_tag_mismatch expected_sequence_number = expected_sequence_number + 1 -``` +``` ## Security DARE provides confidentiality and integrity of the encrypted data as long as the encryption key is never reused. This means that a **different** encryption key **must** be used for every data -stream. See Appendix A for recommendations. +stream. See Appendix A for recommendations. If the same encryption key is used to encrypt two different data streams, an attacker is able to -exchange packages with the same sequence number. This means that the attacker is able to replace +exchange packages with the same sequence number. This means that the attacker is able to replace any package of a sequence with another package as long as: - - Both packages are encrypted with the same key. - - The sequence numbers of both packages are equal. + +- Both packages are encrypted with the same key. +- The sequence numbers of both packages are equal. If two data streams are encrypted with the same key the attacker will not be able to decrypt any package of those streams without breaking the cipher as long as the nonces are different. To be more precise the attacker may only be able to decrypt a package if: - - There is another package encrypted with the same key. - - The sequence number and nonce of those two packages (encrypted with the same key) are equal. + +- There is another package encrypted with the same key. +- The sequence number and nonce of those two packages (encrypted with the same key) are equal. As long as the nonce of a sequence of packages differs from every other nonce (and the nonce is repeated within one sequence - which is **recommended**) the attacker will not be able to decrypt @@ -199,12 +204,12 @@ It is sufficient when the nonces differ from each other in at least one bit. DARE needs a unique encryption key per data stream. The best approach to ensure that the keys are unique is to derive every encryption key from a master key. Therefore a key derivation function -(KDF) - e.g. HKDF, BLAKE2X or HChaCha20 - can be used. The master key itself may be derived from +(KDF) - e.g. HKDF, BLAKE2X or HChaCha20 - can be used. The master key itself may be derived from a password using functions like Argon2 or scrypt. Deriving those keys is the responsibility of the users of DARE. It is **not recommended** to derive encryption keys from a master key and an identifier (like the -file path). If a different data stream is stored under the same identifier - e.g. overwriting the +file path). If a different data stream is stored under the same identifier - e.g. overwriting the data - the derived key would be the same for both streams. Instead encryption keys should be derived from a master key and a random value. It is not required diff --git a/vendor/github.com/minio/sio/README.md b/vendor/github.com/minio/sio/README.md index 4ddf3e8..3f93e6f 100644 --- a/vendor/github.com/minio/sio/README.md +++ b/vendor/github.com/minio/sio/README.md @@ -1,13 +1,16 @@ -[![Godoc Reference](https://godoc.org/github.com/minio/sio?status.svg)](https://godoc.org/github.com/minio/sio) -[![Travis CI](https://travis-ci.org/minio/sio.svg?branch=master)](https://travis-ci.org/minio/sio) -[![Go Report Card](https://goreportcard.com/badge/minio/sio)](https://goreportcard.com/report/minio/sio) +[![Go Reference](https://pkg.go.dev/badge/github.com/minio/sio.svg)](https://pkg.go.dev/github.com/minio/sio) +[![Go](https://github.com/minio/sio/actions/workflows/go.yml/badge.svg)](https://github.com/minio/sio/actions/workflows/go.yml) +[![Go Report Card](https://goreportcard.com/badge/github.com/minio/sio)](https://goreportcard.com/report/github.com/minio/sio) +[![Security](https://img.shields.io/badge/Security-Policy-blue)](SECURITY.md) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) # Secure IO + ## Go implementation of the Data At Rest Encryption (DARE) format. ## Introduction -It is a common problem to store data securely - especially on untrusted remote storage. +It is a common problem to store data securely - especially on untrusted remote storage. One solution to this problem is cryptography. Before data is stored it is encrypted to ensure that the data is confidential. Unfortunately encrypting data is not enough to prevent more sophisticated attacks. Anyone who has access to the stored data can try to @@ -15,9 +18,10 @@ manipulate the data - even if the data is encrypted. To prevent these kinds of attacks the data must be encrypted in a tamper-resistant way. This means an attacker should not be able to: - - Read the stored data - this is achieved by modern encryption algorithms. - - Modify the data by changing parts of the encrypted data. - - Rearrange or reorder parts of the encrypted data. + +- Read the stored data - this is achieved by modern encryption algorithms. +- Modify the data by changing parts of the encrypted data. +- Rearrange or reorder parts of the encrypted data. Authenticated encryption schemes (AE) - like AES-GCM or ChaCha20-Poly1305 - encrypt and authenticate data. Any modification to the encrypted data (ciphertext) is detected while @@ -26,19 +30,19 @@ kinds of data manipulation. All modern AE schemes produce an authentication tag which is verified after the ciphertext is decrypted. If a large amount of data is decrypted it is not always possible to buffer -all decrypted data until the authentication tag is verified. Returning unauthenticated +all decrypted data until the authentication tag is verified. Returning unauthenticated data has the same issues like encrypting data without authentication. Splitting the data into small chunks fixes the problem of deferred authentication checks -but introduces a new one. The chunks can be reordered - e.g. exchanging chunk 1 and 2 - +but introduces a new one. The chunks can be reordered - e.g. exchanging chunk 1 and 2 - because every chunk is encrypted separately. Therefore the order of the chunks must be -encoded somehow into the chunks itself to be able to detect rearranging any number of -chunks. +encoded somehow into the chunks itself to be able to detect rearranging any number of +chunks. -This project specifies a [format](https://github.com/minio/sio/blob/master/DARE.md) for +This project specifies a [format](https://github.com/minio/sio/blob/master/DARE.md) for en/decrypting an arbitrary data stream and gives some [recommendations](https://github.com/minio/sio/blob/master/DARE.md#appendices) about how to use and implement data at rest encryption (DARE). Additionally this project -provides a reference implementation in Go. +provides a reference implementation in Go. ## Applications @@ -47,25 +51,34 @@ with a very simple reorder protection mechanism to build a tamper-resistant encr scheme. DARE can be used to encrypt files, backups and even large object storage systems. Its main properties are: - - Security and high performance by relying on modern AEAD ciphers - - Small overhead - encryption increases the amount of data by ~0.05% - - Support for long data streams - up to 256 TB under the same key - - Random access - arbitrary sequences / ranges can be decrypted independently + +- Security and high performance by relying on modern AEAD ciphers +- Small overhead - encryption increases the amount of data by ~0.05% +- Support for long data streams - up to 256 TB under the same key +- Random access - arbitrary sequences / ranges can be decrypted independently **Install:** `go get -u github.com/minio/sio` -DARE and `github.com/minio/sio` are finalized and can be used in production. +DARE and `github.com/minio/sio` are stable and production-ready. We also provide a CLI tool to en/decrypt arbitrary data streams directly from your command line: -**Install ncrypt:** `go get -u github.com/minio/sio/cmd/ncrypt && ncrypt -h` +**Install ncrypt:** `go install github.com/minio/sio/cmd/ncrypt@latest && ncrypt -h` + +## Contributing + +We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. + +## Security + +For security vulnerability reports, please see our [Security Policy](SECURITY.md). ## Performance -Cipher | 8 KB | 64 KB | 512 KB | 1 MB ------------------ | -------- | --------- | --------- | -------- -AES_256_GCM | 90 MB/s | 1.96 GB/s | 2.64 GB/s | 2.83 GB/s -CHACHA20_POLY1305 | 97 MB/s | 1.23 GB/s | 1.54 GB/s | 1.57 GB/s +| Cipher | 8 KB | 64 KB | 512 KB | 1 MB | +| ----------------- | ------- | --------- | --------- | --------- | +| AES_256_GCM | 90 MB/s | 1.96 GB/s | 2.64 GB/s | 2.83 GB/s | +| CHACHA20_POLY1305 | 97 MB/s | 1.23 GB/s | 1.54 GB/s | 1.57 GB/s | -*On i7-6500U 2 x 2.5 GHz | Linux 4.10.0-32-generic | Go 1.8.3 | AES-NI & AVX2* \ No newline at end of file +_On i7-6500U 2 x 2.5 GHz | Linux 4.10.0-32-generic | Go 1.8.3 | AES-NI & AVX2_ diff --git a/vendor/github.com/minio/sio/SECURITY.md b/vendor/github.com/minio/sio/SECURITY.md new file mode 100644 index 0000000..3cfce70 --- /dev/null +++ b/vendor/github.com/minio/sio/SECURITY.md @@ -0,0 +1,130 @@ +# Security Policy + +## Supported Versions + +We actively support the following versions of `sio`: + +| Version | Supported | +| ------- | ------------------ | +| latest | :white_check_mark: | + +We recommend always using the latest version to ensure you have the most recent security updates. + +## Reporting a Vulnerability + +The MinIO team takes security vulnerabilities seriously. We appreciate your efforts to responsibly disclose your findings. + +### How to Report + +**Please do NOT report security vulnerabilities through public GitHub issues.** + +Instead, please report security vulnerabilities by emailing: + +**security@min.io** + +Include the following information in your report: + +- Type of vulnerability (e.g., buffer overflow, authentication bypass, cryptographic weakness) +- Full paths of source file(s) related to the vulnerability +- Location of the affected source code (tag/branch/commit or direct URL) +- Step-by-step instructions to reproduce the issue +- Proof-of-concept or exploit code (if available) +- Impact of the vulnerability, including how an attacker might exploit it + +### What to Expect + +- **Acknowledgment**: You will receive an acknowledgment of your report within 48 hours. +- **Communication**: We will keep you informed of the progress toward a fix and public disclosure. +- **Credit**: We will credit you in the security advisory (unless you prefer to remain anonymous). +- **Timeline**: We aim to patch critical vulnerabilities within 30 days of responsible disclosure. + +## Security Best Practices + +When using `sio`, follow these security best practices: + +### Key Management + +1. **Never reuse encryption keys**: Each data stream should use a unique key derived from a master key +2. **Use a KDF**: Derive per-stream keys using HKDF, BLAKE2X, or similar with unique context +3. **Secure key storage**: Store master keys in hardware security modules (HSMs) or key management services +4. **Key rotation**: Implement regular key rotation policies + +### Implementation + +1. **Verify authenticity**: Always check for sio.Error types which indicate authentication failures +2. **Handle errors**: Never ignore decryption errors or continue processing unauthenticated data +3. **Memory safety**: Be aware that decrypted data must be explicitly cleared from memory if needed +4. **Random sources**: Use crypto/rand.Reader for all random value generation +5. **Version pinning**: Pin specific versions in production and test updates before deployment + +### Known Limitations + +1. **Key reuse**: Reusing keys across different data streams allows package-level replay attacks +2. **Maximum size**: Single encrypted streams are limited to 256 TB +3. **Sequence numbers**: Limited to 2^32 packages per stream (~256 TB at 64KB packages) + +## Cryptographic Design + +### Algorithms + +- **AES-256-GCM**: Authenticated encryption with 256-bit keys (when hardware acceleration available) +- **ChaCha20-Poly1305**: Authenticated encryption with 256-bit keys (software fallback) + +### Security Properties + +`sio` provides: + +- **Confidentiality**: Data cannot be read without the correct key +- **Integrity**: Modifications to ciphertext are detected during decryption +- **Authenticity**: Data origin is verified through AEAD tags +- **Reorder protection**: Sequence numbers prevent package reordering + +### Attack Resistance + +`sio` is designed to resist: + +- Chosen-plaintext attacks (CPA) +- Chosen-ciphertext attacks (CCA) +- Package reordering attacks +- Truncation attacks (V2.0 with final package flag) + +### Not Protected Against + +`sio` does NOT protect against: + +- Key compromise +- Side-channel attacks (timing, power analysis) on the underlying cipher +- Replay attacks when keys are reused +- Attacks on the key derivation or storage mechanisms + +## Audit History + +- **2018**: Initial implementation review +- **2024**: Ongoing maintenance and security updates +- **TBD**: Formal cryptographic audit (planned) + +## Security Updates + +Security updates will be published as: + +- GitHub Security Advisories +- Release notes with [SECURITY] tags +- Updates to this SECURITY.md file + +Subscribe to repository releases to be notified of security updates. + +## References + +- [DARE Specification](DARE.md) +- [MinIO Security](https://min.io/security) +- [Go Cryptography Policy](https://golang.org/security) + +## Hall of Fame + +We appreciate security researchers who have responsibly disclosed vulnerabilities: + +(No vulnerabilities disclosed yet) + +--- + +Last updated: 2025-01-10 diff --git a/vendor/github.com/minio/sio/dare.go b/vendor/github.com/minio/sio/dare.go index 5aaf4c0..0c17dc5 100644 --- a/vendor/github.com/minio/sio/dare.go +++ b/vendor/github.com/minio/sio/dare.go @@ -29,7 +29,7 @@ func (h headerV10) Len() int { return int(binary.LittleEndia func (h headerV10) SequenceNumber() uint32 { return binary.LittleEndian.Uint32(h[4:]) } func (h headerV10) SetVersion() { h[0] = Version10 } func (h headerV10) SetCipher(suite byte) { h[1] = suite } -func (h headerV10) SetLen(length int) { binary.LittleEndian.PutUint16(h[2:], uint16(length-1)) } +func (h headerV10) SetLen(length int) { binary.LittleEndian.PutUint16(h[2:], uint16(length-1)) } //nolint:gosec // Expected conversion func (h headerV10) SetSequenceNumber(num uint32) { binary.LittleEndian.PutUint32(h[4:], num) } func (h headerV10) SetRand(randVal []byte) { copy(h[8:headerSize], randVal) } func (h headerV10) Nonce() []byte { return h[4:headerSize] } @@ -49,7 +49,7 @@ func (h headerV20) SetVersion() { h[0] = Version20 } func (h headerV20) Cipher() byte { return h[1] } func (h headerV20) SetCipher(cipher byte) { h[1] = cipher } func (h headerV20) Length() int { return int(binary.LittleEndian.Uint16(h[2:4])) + 1 } -func (h headerV20) SetLength(length int) { binary.LittleEndian.PutUint16(h[2:4], uint16(length-1)) } +func (h headerV20) SetLength(length int) { binary.LittleEndian.PutUint16(h[2:4], uint16(length-1)) } //nolint:gosec // Expected conversion func (h headerV20) IsFinal() bool { return h[4]&0x80 == 0x80 } func (h headerV20) Nonce() []byte { return h[4:headerSize] } func (h headerV20) AddData() []byte { return h[:4] } @@ -184,12 +184,12 @@ func newAuthEncV20(cfg *Config) (authEncV20, error) { }, nil } -func (ae *authEncV20) Seal(dst, src []byte) { ae.seal(dst, src, false) } -func (ae *authEncV20) SealFinal(dst, src []byte) { ae.seal(dst, src, true) } +func (ae *authEncV20) Seal(dst, src []byte) error { return ae.seal(dst, src, false) } +func (ae *authEncV20) SealFinal(dst, src []byte) error { return ae.seal(dst, src, true) } -func (ae *authEncV20) seal(dst, src []byte, finalize bool) { +func (ae *authEncV20) seal(dst, src []byte, finalize bool) error { if ae.finalized { // callers are not supposed to call Seal(Final) after a SealFinal call happened - panic("sio: cannot seal any package after final one") + return errSealAfterFinal } ae.finalized = finalize @@ -205,6 +205,7 @@ func (ae *authEncV20) seal(dst, src []byte, finalize bool) { ae.Cipher.Seal(dst[headerSize:headerSize], nonce[:], src, header.AddData()) ae.SeqNum++ + return nil } type authDecV20 struct { diff --git a/vendor/github.com/minio/sio/reader-v1.go b/vendor/github.com/minio/sio/reader-v1.go index e20233d..91711e2 100644 --- a/vendor/github.com/minio/sio/reader-v1.go +++ b/vendor/github.com/minio/sio/reader-v1.go @@ -24,6 +24,7 @@ type encReaderV10 struct { src io.Reader buffer packageV10 + recycle func() // Returns the buffer to the pool offset int payloadSize int stateErr error @@ -36,19 +37,16 @@ func encryptReaderV10(src io.Reader, config *Config) (*encReaderV10, error) { if err != nil { return nil, err } + buffer, recycle := getBuffer() return &encReaderV10{ authEncV10: ae, src: src, - buffer: packageBufferPool.Get().([]byte)[:maxPackageSize], + buffer: buffer, + recycle: recycle, payloadSize: config.PayloadSize, }, nil } -func (r *encReaderV10) recycle() { - recyclePackageBufferPool(r.buffer) - r.buffer = nil -} - func (r *encReaderV10) Read(p []byte) (int, error) { if r.stateErr != nil { return 0, r.stateErr @@ -99,6 +97,7 @@ type decReaderV10 struct { src io.Reader buffer packageV10 + recycle func() // Returns the buffer to the pool offset int stateErr error } @@ -110,18 +109,15 @@ func decryptReaderV10(src io.Reader, config *Config) (*decReaderV10, error) { if err != nil { return nil, err } + buffer, recycle := getBuffer() return &decReaderV10{ authDecV10: ad, src: src, - buffer: packageBufferPool.Get().([]byte)[:maxPackageSize], + buffer: buffer, + recycle: recycle, }, nil } -func (r *decReaderV10) recycle() { - recyclePackageBufferPool(r.buffer) - r.buffer = nil -} - func (r *decReaderV10) Read(p []byte) (n int, err error) { if r.stateErr != nil { return 0, r.stateErr @@ -132,7 +128,7 @@ func (r *decReaderV10) Read(p []byte) (n int, err error) { if len(p) < remaining { n = copy(p, payload[r.offset:+r.offset+len(p)]) r.offset += n - return + return n, err } n = copy(p, payload[r.offset:r.offset+remaining]) p = p[remaining:] @@ -226,15 +222,17 @@ func (r *decReaderAtV10) ReadAt(p []byte, offset int64) (n int, err error) { return 0, errUnexpectedSize } - buffer := packageBufferPool.Get().([]byte)[:maxPackageSize] - defer recyclePackageBufferPool(buffer) + buffer, recycle := getBuffer() + defer recycle() + decReader := decReaderV10{ authDecV10: r.ad, src: §ionReader{r.src, t * maxPackageSize}, buffer: packageV10(buffer), + recycle: recycle, offset: 0, } - decReader.SeqNum = uint32(t) + decReader.SeqNum = uint32(t) //nolint:gosec // Safe conversion if k := offset % int64(maxPayloadSize); k > 0 { if _, err := io.CopyN(io.Discard, &decReader, k); err != nil { return 0, err diff --git a/vendor/github.com/minio/sio/reader-v2.go b/vendor/github.com/minio/sio/reader-v2.go index b8794bc..dd5aaf2 100644 --- a/vendor/github.com/minio/sio/reader-v2.go +++ b/vendor/github.com/minio/sio/reader-v2.go @@ -25,6 +25,7 @@ type encReaderV20 struct { src io.Reader buffer packageV20 + recycle func() // Returns the buffer to the pool offset int lastByte byte stateErr error @@ -32,17 +33,20 @@ type encReaderV20 struct { firstRead bool } -var packageBufferPool = sync.Pool{New: func() interface{} { return make([]byte, maxPackageSize) }} +var packageBufferPool = sync.Pool{ + New: func() any { + b := make([]byte, maxPackageSize) + return &b + }, +} -func recyclePackageBufferPool(b []byte) { - if cap(b) < maxPackageSize { - return - } - // Clear so we don't potentially leak info between callers - for i := range b { - b[i] = 0 - } - packageBufferPool.Put(b) +func getBuffer() ([]byte, func()) { + p := packageBufferPool.Get().(*[]byte) + return *p, sync.OnceFunc(func() { + toZero := *p + clear(toZero[:cap(toZero)]) + packageBufferPool.Put(p) + }) } // encryptReaderV20 returns an io.Reader wrapping the given io.Reader. @@ -52,19 +56,17 @@ func encryptReaderV20(src io.Reader, config *Config) (*encReaderV20, error) { if err != nil { return nil, err } + + buffer, recycle := getBuffer() return &encReaderV20{ authEncV20: ae, src: src, - buffer: packageBufferPool.Get().([]byte)[:maxPackageSize], + buffer: buffer, + recycle: recycle, firstRead: true, }, nil } -func (r *encReaderV20) recycle() { - recyclePackageBufferPool(r.buffer) - r.buffer = nil -} - func (r *encReaderV20) Read(p []byte) (n int, err error) { if r.stateErr != nil { return 0, r.stateErr @@ -106,11 +108,15 @@ func (r *encReaderV20) Read(p []byte) (n int, err error) { return n, err // failed to read from src } if err == io.EOF || err == io.ErrUnexpectedEOF { // read less than 64KB -> final package - r.SealFinal(p, r.buffer[headerSize:headerSize+1+nn]) + if sealErr := r.SealFinal(p, r.buffer[headerSize:headerSize+1+nn]); sealErr != nil { + return n, sealErr + } return n + headerSize + tagSize + 1 + nn, io.EOF } r.lastByte = r.buffer[headerSize+maxPayloadSize] // save last read byte for the next package - r.Seal(p, r.buffer[headerSize:headerSize+maxPayloadSize]) + if sealErr := r.Seal(p, r.buffer[headerSize:headerSize+maxPayloadSize]); sealErr != nil { + return n, sealErr + } p = p[maxPackageSize:] n += maxPackageSize } @@ -123,7 +129,11 @@ func (r *encReaderV20) Read(p []byte) (n int, err error) { return n, err // failed to read from src } if err == io.EOF || err == io.ErrUnexpectedEOF { // read less than 64KB -> final package - r.SealFinal(r.buffer, r.buffer[headerSize:headerSize+1+nn]) + if sealErr := r.SealFinal(r.buffer, r.buffer[headerSize:headerSize+1+nn]); sealErr != nil { + r.stateErr = sealErr + r.recycle() + return n, sealErr + } if len(p) > r.buffer.Length() { n += copy(p, r.buffer[:r.buffer.Length()]) r.stateErr = io.EOF @@ -132,7 +142,11 @@ func (r *encReaderV20) Read(p []byte) (n int, err error) { } } else { r.lastByte = r.buffer[headerSize+maxPayloadSize] // save last read byte for the next package - r.Seal(r.buffer, r.buffer[headerSize:headerSize+maxPayloadSize]) + if sealErr := r.Seal(r.buffer, r.buffer[headerSize:headerSize+maxPayloadSize]); sealErr != nil { + r.stateErr = sealErr + r.recycle() + return n, sealErr + } } r.offset = copy(p, r.buffer[:len(p)]) // len(p) < len(r.buffer) - otherwise we would be still in the for-loop n += r.offset @@ -146,6 +160,7 @@ type decReaderV20 struct { stateErr error buffer packageV20 + recycle func() offset int } @@ -156,18 +171,15 @@ func decryptReaderV20(src io.Reader, config *Config) (*decReaderV20, error) { if err != nil { return nil, err } + buffer, recycle := getBuffer() return &decReaderV20{ authDecV20: ad, src: src, - buffer: packageBufferPool.Get().([]byte)[:maxPackageSize], + buffer: buffer, + recycle: recycle, }, nil } -func (r *decReaderV20) recycle() { - recyclePackageBufferPool(r.buffer) - r.buffer = nil -} - func (r *decReaderV20) Read(p []byte) (n int, err error) { if r.stateErr != nil { return 0, r.stateErr @@ -296,14 +308,17 @@ func (r *decReaderAtV20) ReadAt(p []byte, offset int64) (n int, err error) { t-- } + buffer, recycle := getBuffer() + defer recycle() + decReader := decReaderV20{ authDecV20: r.ad, src: §ionReader{r.src, t * maxPackageSize}, - buffer: packageBufferPool.Get().([]byte)[:maxPackageSize], + buffer: buffer, + recycle: recycle, offset: 0, } - defer decReader.recycle() - decReader.SeqNum = uint32(t) + decReader.SeqNum = uint32(t) //nolint:gosec // Safe conversion if k > 0 { if _, err := io.CopyN(io.Discard, &decReader, k); err != nil { return 0, err @@ -312,7 +327,7 @@ func (r *decReaderAtV20) ReadAt(p []byte, offset int64) (n int, err error) { for n < len(p) && err == nil { var nn int - nn, err = (&decReader).Read(p[n:]) + nn, err = decReader.Read(p[n:]) n += nn } if err == io.EOF && n == len(p) { diff --git a/vendor/github.com/minio/sio/sio.go b/vendor/github.com/minio/sio/sio.go index 32985d3..8f379dd 100644 --- a/vendor/github.com/minio/sio/sio.go +++ b/vendor/github.com/minio/sio/sio.go @@ -22,6 +22,7 @@ import ( "crypto/cipher" "crypto/rand" "errors" + "fmt" "io" "runtime" @@ -45,7 +46,24 @@ const ( // supportsAES indicates whether the CPU provides hardware support for AES-GCM. // AES-GCM should only be selected as default cipher if there's hardware support. -var supportsAES = (cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ) || runtime.GOARCH == "s390x" +var supportsAES = detectAESSupport() + +func detectAESSupport() bool { + // x86/x86_64: Check for AES-NI and PCLMULQDQ instructions + if runtime.GOARCH == "amd64" || runtime.GOARCH == "386" { + return cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ + } + // ARM64: Check for AES and PMULL crypto extensions + if runtime.GOARCH == "arm64" { + return cpu.ARM64.HasAES && cpu.ARM64.HasPMULL + } + // s390x: Has built-in hardware acceleration + if runtime.GOARCH == "s390x" { + return true + } + // For other architectures, default to ChaCha20-Poly1305 + return false +} const ( keySize = 32 @@ -83,9 +101,11 @@ var ( errPackageOutOfOrder = Error{"sio: sequence number mismatch"} // Version 2.0 specific - errNonceMismatch = Error{"sio: header nonce mismatch"} - errUnexpectedEOF = Error{"sio: unexpected EOF"} - errUnexpectedData = Error{"sio: unexpected data after final package"} + errNonceMismatch = Error{"sio: header nonce mismatch"} + errUnexpectedEOF = Error{"sio: unexpected EOF"} + errUnexpectedData = Error{"sio: unexpected data after final package"} + errSealAfterFinal = Error{"sio: cannot seal any package after final one"} + errWriteAfterClose = Error{"sio: write to stream after close"} ) // Error is the error returned by an io.Reader or io.Writer @@ -179,9 +199,13 @@ func DecryptedSize(size uint64) (uint64, error) { func Encrypt(dst io.Writer, src io.Reader, config Config) (n int64, err error) { encReader, err := EncryptReader(src, config) if err != nil { - return 0, err + return 0, fmt.Errorf("sio: failed to create encryption reader: %w", err) } - return io.CopyBuffer(dst, encReader, make([]byte, headerSize+maxPayloadSize+tagSize)) + n, err = io.CopyBuffer(dst, encReader, make([]byte, headerSize+maxPayloadSize+tagSize)) + if err != nil { + return n, fmt.Errorf("sio: encryption failed: %w", err) + } + return n, nil } // Decrypt reads from src until it encounters an io.EOF and decrypts all received @@ -194,9 +218,13 @@ func Encrypt(dst io.Writer, src io.Reader, config Config) (n int64, err error) { func Decrypt(dst io.Writer, src io.Reader, config Config) (n int64, err error) { decReader, err := DecryptReader(src, config) if err != nil { - return 0, err + return 0, fmt.Errorf("sio: failed to create decryption reader: %w", err) + } + n, err = io.CopyBuffer(dst, decReader, make([]byte, maxPayloadSize)) + if err != nil { + return n, fmt.Errorf("sio: decryption failed: %w", err) } - return io.CopyBuffer(dst, decReader, make([]byte, maxPayloadSize)) + return n, nil } // DecryptBuffer decrypts all received data in src. diff --git a/vendor/github.com/minio/sio/writer-v1.go b/vendor/github.com/minio/sio/writer-v1.go index 5aa247a..2997f4f 100644 --- a/vendor/github.com/minio/sio/writer-v1.go +++ b/vendor/github.com/minio/sio/writer-v1.go @@ -14,13 +14,16 @@ package sio -import "io" +import ( + "io" +) type decWriterV10 struct { authDecV10 dst io.Writer buffer packageV10 + recycle func() // Returns the buffer to the pool offset int closeErr error } @@ -35,24 +38,26 @@ func decryptWriterV10(dst io.Writer, config *Config) (*decWriterV10, error) { if err != nil { return nil, err } - buf := packageBufferPool.Get().([]byte)[:maxPackageSize] - for i := range buf { - buf[i] = 0 - } + + buffer, recycle := getBuffer() return &decWriterV10{ authDecV10: ad, dst: dst, - buffer: buf, + buffer: buffer, + recycle: recycle, }, nil } func (w *decWriterV10) Write(p []byte) (n int, err error) { + if w.closeErr != nil { + return 0, w.closeErr + } if w.offset > 0 && w.offset < headerSize { // buffer the header -> special code b/c we don't know when to decrypt without header remaining := headerSize - w.offset if len(p) < remaining { n = copy(w.buffer[w.offset:], p) w.offset += n - return + return n, err } n = copy(w.buffer[w.offset:], p[:remaining]) p = p[remaining:] @@ -67,9 +72,13 @@ func (w *decWriterV10) Write(p []byte) (n int, err error) { } n += copy(w.buffer[w.offset:], p[:remaining]) if err = w.Open(w.buffer.Payload(), w.buffer[:w.buffer.Length()]); err != nil { + w.recycle() + w.closeErr = err return n, err } if err = flush(w.dst, w.buffer.Payload()); err != nil { // write to underlying io.Writer + w.recycle() + w.closeErr = err return n, err } p = p[remaining:] @@ -83,9 +92,13 @@ func (w *decWriterV10) Write(p []byte) (n int, err error) { return n, err } if err = w.Open(w.buffer[headerSize:packageLen-tagSize], p[:packageLen]); err != nil { + w.recycle() + w.closeErr = err return n, err } if err = flush(w.dst, w.buffer[headerSize:packageLen-tagSize]); err != nil { // write to underlying io.Writer + w.recycle() + w.closeErr = err return n, err } p = p[packageLen:] @@ -99,34 +112,39 @@ func (w *decWriterV10) Write(p []byte) (n int, err error) { } func (w *decWriterV10) Close() (err error) { - if w.buffer == nil { + defer w.recycle() + + if w.closeErr != nil { + if dst, ok := w.dst.(io.Closer); ok { + _ = dst.Close() + } return w.closeErr } - defer func() { - w.closeErr = err - recyclePackageBufferPool(w.buffer) - w.buffer = nil - }() + if w.offset > 0 { if w.offset <= headerSize+tagSize { - return errInvalidPayloadSize // the payload is always > 0 - } - header := headerV10(w.buffer[:headerSize]) - if w.offset < headerSize+header.Len()+tagSize { - return errInvalidPayloadSize // there is less data than specified by the header + w.closeErr = errInvalidPayloadSize // the payload is always > 0 } - if err = w.Open(w.buffer.Payload(), w.buffer[:w.buffer.Length()]); err != nil { - return err - } - if err = flush(w.dst, w.buffer.Payload()); err != nil { // write to underlying io.Writer - return err + if w.closeErr == nil { + header := headerV10(w.buffer[:headerSize]) + if w.offset < headerSize+header.Len()+tagSize { + w.closeErr = errInvalidPayloadSize + } + if w.closeErr == nil { + w.closeErr = w.Open(w.buffer.Payload(), w.buffer[:w.buffer.Length()]) + } + if w.closeErr == nil { + w.closeErr = flush(w.dst, w.buffer.Payload()) + } } } if dst, ok := w.dst.(io.Closer); ok { - return dst.Close() + if err := dst.Close(); w.closeErr == nil { + w.closeErr = err + } } - return nil + return w.closeErr } type encWriterV10 struct { @@ -134,6 +152,7 @@ type encWriterV10 struct { dst io.Writer buffer packageV10 + recycle func() // Returns the buffer to the pool offset int payloadSize int closeErr error @@ -150,33 +169,43 @@ func encryptWriterV10(dst io.Writer, config *Config) (*encWriterV10, error) { if err != nil { return nil, err } + + buffer, recycle := getBuffer() return &encWriterV10{ authEncV10: ae, dst: dst, - buffer: packageBufferPool.Get().([]byte)[:maxPackageSize], + buffer: buffer, + recycle: recycle, payloadSize: config.PayloadSize, }, nil } func (w *encWriterV10) Write(p []byte) (n int, err error) { + if w.closeErr != nil { + return 0, w.closeErr + } if w.offset > 0 { // buffer the plaintext remaining := w.payloadSize - w.offset if len(p) < remaining { n = copy(w.buffer[headerSize+w.offset:], p) w.offset += n - return + return n, err } n = copy(w.buffer[headerSize+w.offset:], p[:remaining]) w.Seal(w.buffer, w.buffer[headerSize:headerSize+w.payloadSize]) if err = flush(w.dst, w.buffer[:w.buffer.Length()]); err != nil { // write to underlying io.Writer + w.recycle() + w.closeErr = err return n, err } p = p[remaining:] w.offset = 0 } for len(p) >= w.payloadSize { - w.Seal(w.buffer[:], p[:w.payloadSize]) + w.Seal(w.buffer, p[:w.payloadSize]) if err = flush(w.dst, w.buffer[:w.buffer.Length()]); err != nil { // write to underlying io.Writer + w.recycle() + w.closeErr = err return n, err } p = p[w.payloadSize:] @@ -186,29 +215,29 @@ func (w *encWriterV10) Write(p []byte) (n int, err error) { w.offset = copy(w.buffer[headerSize:], p) n += w.offset } - return + return n, err } func (w *encWriterV10) Close() (err error) { - if w.buffer == nil { + defer w.recycle() + + if w.closeErr != nil { + if dst, ok := w.dst.(io.Closer); ok { + _ = dst.Close() + } return w.closeErr } - defer func() { - w.closeErr = err - recyclePackageBufferPool(w.buffer) - w.buffer = nil - }() if w.offset > 0 { w.Seal(w.buffer[:], w.buffer[headerSize:headerSize+w.offset]) - if err := flush(w.dst, w.buffer[:w.buffer.Length()]); err != nil { // write to underlying io.Writer - return err - } + w.closeErr = flush(w.dst, w.buffer[:w.buffer.Length()]) // write to underlying io.Writer } if dst, ok := w.dst.(io.Closer); ok { - return dst.Close() + if err := dst.Close(); w.closeErr == nil { + w.closeErr = err + } } - return nil + return w.closeErr } func flush(w io.Writer, p []byte) error { diff --git a/vendor/github.com/minio/sio/writer-v2.go b/vendor/github.com/minio/sio/writer-v2.go index c48afd8..2b15e26 100644 --- a/vendor/github.com/minio/sio/writer-v2.go +++ b/vendor/github.com/minio/sio/writer-v2.go @@ -23,6 +23,7 @@ type encWriterV20 struct { dst io.Writer buffer packageV20 + recycle func() // Returns the buffer to the pool offset int closeErr error } @@ -38,18 +39,23 @@ func encryptWriterV20(dst io.Writer, config *Config) (*encWriterV20, error) { if err != nil { return nil, err } + buffer, recycle := getBuffer() return &encWriterV20{ authEncV20: ae, dst: dst, - buffer: packageBufferPool.Get().([]byte)[:maxPackageSize], + buffer: buffer, + recycle: recycle, }, nil } func (w *encWriterV20) Write(p []byte) (n int, err error) { + if w.closeErr != nil { + return 0, w.closeErr + } if w.finalized { // The caller closed the encWriterV20 instance (called encWriterV20.Close()). // This is a bug in the calling code - Write after Close is not allowed. - panic("sio: write to stream after close") + return 0, errWriteAfterClose } if w.offset > 0 { // buffer the plaintext data remaining := maxPayloadSize - w.offset @@ -58,16 +64,28 @@ func (w *encWriterV20) Write(p []byte) (n int, err error) { return len(p), nil } n = copy(w.buffer[headerSize+w.offset:], p[:remaining]) - w.Seal(w.buffer, w.buffer[headerSize:headerSize+maxPayloadSize]) + if err = w.Seal(w.buffer, w.buffer[headerSize:headerSize+maxPayloadSize]); err != nil { + w.recycle() + w.closeErr = err + return n, err + } if err = flush(w.dst, w.buffer); err != nil { // write to underlying io.Writer + w.recycle() + w.closeErr = err return n, err } p = p[remaining:] w.offset = 0 } for len(p) > maxPayloadSize { // > is important here to call Seal (not SealFinal) only if there is at least on package left - see: Close() - w.Seal(w.buffer, p[:maxPayloadSize]) + if err = w.Seal(w.buffer, p[:maxPayloadSize]); err != nil { + w.recycle() + w.closeErr = err + return n, err + } if err = flush(w.dst, w.buffer); err != nil { // write to underlying io.Writer + w.recycle() + w.closeErr = err return n, err } p = p[maxPayloadSize:] @@ -81,26 +99,29 @@ func (w *encWriterV20) Write(p []byte) (n int, err error) { } func (w *encWriterV20) Close() (err error) { - if w.buffer == nil { + defer w.recycle() + + if w.closeErr != nil { + if closer, ok := w.dst.(io.Closer); ok { + _ = closer.Close() + } return w.closeErr } - defer func() { - w.closeErr = err - recyclePackageBufferPool(w.buffer) - w.buffer = nil - }() if w.offset > 0 { // true if at least one Write call happened - w.SealFinal(w.buffer, w.buffer[headerSize:headerSize+w.offset]) - if err = flush(w.dst, w.buffer[:headerSize+w.offset+tagSize]); err != nil { // write to underlying io.Writer - return err + if err := w.SealFinal(w.buffer, w.buffer[headerSize:headerSize+w.offset]); err != nil { + w.closeErr = err + } else { + w.closeErr = flush(w.dst, w.buffer[:headerSize+w.offset+tagSize]) // write to underlying io.Writer } w.offset = 0 } if closer, ok := w.dst.(io.Closer); ok { - return closer.Close() + if err := closer.Close(); w.closeErr == nil { + w.closeErr = err + } } - return nil + return w.closeErr } type decWriterV20 struct { @@ -108,6 +129,7 @@ type decWriterV20 struct { dst io.Writer buffer packageV20 + recycle func() // Returns the buffer to the pool offset int closeErr error } @@ -122,14 +144,19 @@ func decryptWriterV20(dst io.Writer, config *Config) (*decWriterV20, error) { if err != nil { return nil, err } + buffer, recycle := getBuffer() return &decWriterV20{ authDecV20: ad, dst: dst, - buffer: packageBufferPool.Get().([]byte)[:maxPackageSize], + buffer: buffer, + recycle: recycle, }, nil } func (w *decWriterV20) Write(p []byte) (n int, err error) { + if w.closeErr != nil { + return 0, w.closeErr + } if w.offset > 0 { // buffer package remaining := headerSize + maxPayloadSize + tagSize - w.offset if len(p) < remaining { @@ -139,9 +166,13 @@ func (w *decWriterV20) Write(p []byte) (n int, err error) { n = copy(w.buffer[w.offset:], p[:remaining]) plaintext := w.buffer[headerSize : headerSize+maxPayloadSize] if err = w.Open(plaintext, w.buffer); err != nil { + w.recycle() + w.closeErr = err return n, err } if err = flush(w.dst, plaintext); err != nil { // write to underlying io.Writer + w.recycle() + w.closeErr = err return n, err } p = p[remaining:] @@ -150,9 +181,13 @@ func (w *decWriterV20) Write(p []byte) (n int, err error) { for len(p) >= maxPackageSize { plaintext := w.buffer[headerSize : headerSize+maxPayloadSize] if err = w.Open(plaintext, p[:maxPackageSize]); err != nil { + w.recycle() + w.closeErr = err return n, err } if err = flush(w.dst, plaintext); err != nil { // write to underlying io.Writer + w.recycle() + w.closeErr = err return n, err } p = p[maxPackageSize:] @@ -160,7 +195,9 @@ func (w *decWriterV20) Write(p []byte) (n int, err error) { } if len(p) > 0 { if w.finalized { - return n, errUnexpectedData + w.recycle() + w.closeErr = errUnexpectedData + return n, w.closeErr } w.offset = copy(w.buffer[:], p) n += w.offset @@ -169,29 +206,32 @@ func (w *decWriterV20) Write(p []byte) (n int, err error) { } func (w *decWriterV20) Close() (err error) { - if w.buffer == nil { + defer w.recycle() + + if w.closeErr != nil { + if closer, ok := w.dst.(io.Closer); ok { + _ = closer.Close() + } return w.closeErr } - defer func() { - w.closeErr = err - recyclePackageBufferPool(w.buffer) - w.buffer = nil - }() + if w.offset > 0 { if w.offset <= headerSize+tagSize { // the payload is always > 0 - return errInvalidPayloadSize + w.closeErr = errInvalidPayloadSize } - if err = w.Open(w.buffer[headerSize:w.offset-tagSize], w.buffer[:w.offset]); err != nil { - return err + if w.closeErr == nil { + w.closeErr = w.Open(w.buffer[headerSize:w.offset-tagSize], w.buffer[:w.offset]) } - if err = flush(w.dst, w.buffer[headerSize:w.offset-tagSize]); err != nil { // write to underlying io.Writer - return err + if w.closeErr == nil { + w.closeErr = flush(w.dst, w.buffer[headerSize:w.offset-tagSize]) // write to underlying io.Writer } w.offset = 0 } if closer, ok := w.dst.(io.Closer); ok { - return closer.Close() + if err := closer.Close(); w.closeErr == nil { + w.closeErr = err + } } - return nil + return w.closeErr } diff --git a/vendor/modules.txt b/vendor/modules.txt index 50aafa3..5c708d6 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -2,8 +2,8 @@ ## explicit; go 1.18 github.com/BurntSushi/toml github.com/BurntSushi/toml/internal -# github.com/cloudbase/garm-provider-common v0.1.7 -## explicit; go 1.23.0 +# github.com/cloudbase/garm-provider-common v0.1.9 +## explicit; go 1.25.0 github.com/cloudbase/garm-provider-common/cloudconfig github.com/cloudbase/garm-provider-common/defaults github.com/cloudbase/garm-provider-common/errors @@ -40,8 +40,8 @@ github.com/linode/linodego/internal/parseabletime # github.com/mattn/go-isatty v0.0.20 ## explicit; go 1.15 github.com/mattn/go-isatty -# github.com/minio/sio v0.4.1 -## explicit; go 1.18 +# github.com/minio/sio v0.4.3 +## explicit; go 1.24.0 github.com/minio/sio # github.com/pkg/errors v0.9.1 ## explicit