mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-18 12:08:32 +02:00
* s3: skip seed-signature verification in chunked reader when IAM is off When no credentials are configured there is nothing to verify streaming chunk signatures against. Leave the credential unset so the reader decodes the aws-chunked body and skips per-chunk verification, matching the unsigned-streaming path. This also fixes SigV4-signed unsigned-streaming uploads on open gateways, which previously failed in calculateSeedSignature. setupIam() now sets isAuthEnabled to match the credentials it installs, so the signature-verification tests still exercise the enabled path. * s3: decode signed streaming uploads when IAM is disabled AWS SDK v2 clients (Trino, Spark, modern aws-cli) send PutObject bodies as STREAMING-AWS4-HMAC-SHA256-PAYLOAD. On a gateway without S3 auth this was rejected with ErrAuthNotSetup, breaking writes to table buckets and regular buckets alike. The auth check already short-circuits when IAM is off, so route both streaming payload types through the chunked reader. * s3: cover signed trailer streaming uploads without IAM Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
629 lines
20 KiB
Go
629 lines
20 KiB
Go
package s3api
|
|
|
|
// the related code is copied and modified from minio source code
|
|
|
|
/*
|
|
* Minio Cloud Storage, (C) 2016 Minio, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"crypto/sha1"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"hash"
|
|
"hash/crc32"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
|
|
|
"github.com/dustin/go-humanize"
|
|
"github.com/minio/crc64nvme"
|
|
)
|
|
|
|
// calculateSeedSignature - Calculate seed signature in accordance with
|
|
// - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html
|
|
//
|
|
// returns signature, error otherwise if the signature mismatches or any other
|
|
// error while parsing and validating.
|
|
//
|
|
// Only the signature is verified here. The request was already authorized by
|
|
// the Auth middleware, which also evaluates bucket policies; re-running the
|
|
// permission check here does not, and would deny what the middleware allowed.
|
|
func (iam *IdentityAccessManagement) calculateSeedSignature(r *http.Request) (cred *Credential, signature string, region string, service string, date time.Time, errCode s3err.ErrorCode) {
|
|
_, credential, calculatedSignature, authInfo, errCode := iam.verifyV4Signature(r, false)
|
|
if errCode != s3err.ErrNone {
|
|
return nil, "", "", "", time.Time{}, errCode
|
|
}
|
|
|
|
// This check ensures we only proceed for streaming uploads.
|
|
switch authInfo.HashedPayload {
|
|
case streamingContentSHA256, streamingContentSHA256Trailer:
|
|
glog.V(3).Infof("streaming content sha256 (with trailer: %v)", authInfo.HashedPayload == streamingContentSHA256Trailer)
|
|
case streamingUnsignedPayload:
|
|
glog.V(3).Infof("streaming unsigned payload")
|
|
default:
|
|
return nil, "", "", "", time.Time{}, s3err.ErrContentSHA256Mismatch
|
|
}
|
|
|
|
return credential, calculatedSignature, authInfo.Region, authInfo.Service, authInfo.Date, s3err.ErrNone
|
|
}
|
|
|
|
const maxLineLength = 4 * humanize.KiByte // assumed <= bufio.defaultBufSize 4KiB
|
|
|
|
// lineTooLong is generated as chunk header is bigger than 4KiB.
|
|
var errLineTooLong = errors.New("header line too long")
|
|
|
|
// Malformed encoding is generated when chunk header is wrongly formed.
|
|
var errMalformedEncoding = errors.New("malformed chunked encoding")
|
|
|
|
// newChunkedReader returns a new s3ChunkedReader that translates the data read from r
|
|
// out of HTTP "chunked" format before returning it.
|
|
// The s3ChunkedReader returns io.EOF when the final 0-length chunk is read.
|
|
func (iam *IdentityAccessManagement) newChunkedReader(req *http.Request) (io.ReadCloser, s3err.ErrorCode) {
|
|
glog.V(3).Infof("creating a new newSignV4ChunkedReader")
|
|
|
|
contentSha256Header := req.Header.Get("X-Amz-Content-Sha256")
|
|
|
|
var credential *Credential
|
|
var seedSignature, region, service string
|
|
var seedDate time.Time
|
|
var errCode s3err.ErrorCode
|
|
|
|
switch contentSha256Header {
|
|
// Payload for STREAMING signature should be 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD' or 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER'
|
|
case streamingContentSHA256, streamingContentSHA256Trailer:
|
|
glog.V(3).Infof("streaming content sha256 (with trailer: %v)", contentSha256Header == streamingContentSHA256Trailer)
|
|
// With IAM disabled there is no credential to verify against; leave
|
|
// cred nil so chunk signatures are decoded but not verified.
|
|
if iam.isEnabled() {
|
|
credential, seedSignature, region, service, seedDate, errCode = iam.calculateSeedSignature(req)
|
|
if errCode != s3err.ErrNone {
|
|
return nil, errCode
|
|
}
|
|
}
|
|
case streamingUnsignedPayload:
|
|
glog.V(3).Infof("streaming unsigned payload")
|
|
// The seed signature is a SigV4 concept; recompute it only for SigV4-signed
|
|
// requests. JWT-bearer, presigned, and anonymous unsigned-streaming uploads
|
|
// carry no header seed, and verifyV4Signature would fail parsing them.
|
|
if iam.isEnabled() && isRequestSignatureV4(req) {
|
|
// We do not need to pass the seed signature to the Reader as each chunk is not signed,
|
|
// but we do compute it to verify the request's signature.
|
|
_, _, _, _, _, errCode = iam.calculateSeedSignature(req)
|
|
if errCode != s3err.ErrNone {
|
|
return nil, errCode
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get the checksum algorithm from the x-amz-trailer Header.
|
|
amzTrailerHeader := req.Header.Get("x-amz-trailer")
|
|
checksumAlgorithm, err := extractChecksumAlgorithm(amzTrailerHeader)
|
|
|
|
if err != nil {
|
|
glog.V(3).Infof("error extracting checksum algorithm: %v", err)
|
|
return nil, s3err.ErrInvalidRequest
|
|
}
|
|
|
|
checkSumWriter := getCheckSumWriter(checksumAlgorithm)
|
|
hasTrailer := amzTrailerHeader != ""
|
|
|
|
return &s3ChunkedReader{
|
|
cred: credential,
|
|
reader: bufio.NewReader(req.Body),
|
|
seedSignature: seedSignature,
|
|
seedDate: seedDate,
|
|
region: region,
|
|
service: service,
|
|
chunkSHA256Writer: sha256.New(),
|
|
checkSumAlgorithm: checksumAlgorithm.String(),
|
|
checkSumWriter: checkSumWriter,
|
|
state: readChunkHeader,
|
|
iam: iam,
|
|
hasTrailer: hasTrailer,
|
|
}, s3err.ErrNone
|
|
}
|
|
|
|
func extractChecksumAlgorithm(amzTrailerHeader string) (ChecksumAlgorithm, error) {
|
|
// Extract checksum algorithm from the x-amz-trailer header.
|
|
amzTrailerHeader = strings.ToLower(strings.TrimSpace(amzTrailerHeader))
|
|
switch amzTrailerHeader {
|
|
case "x-amz-checksum-crc32":
|
|
return ChecksumAlgorithmCRC32, nil
|
|
case "x-amz-checksum-crc32c":
|
|
return ChecksumAlgorithmCRC32C, nil
|
|
case "x-amz-checksum-crc64nvme":
|
|
return ChecksumAlgorithmCRC64NVMe, nil
|
|
case "x-amz-checksum-sha1":
|
|
return ChecksumAlgorithmSHA1, nil
|
|
case "x-amz-checksum-sha256":
|
|
return ChecksumAlgorithmSHA256, nil
|
|
case "":
|
|
return ChecksumAlgorithmNone, nil
|
|
default:
|
|
return ChecksumAlgorithmNone, errors.New("unsupported checksum algorithm '" + amzTrailerHeader + "'")
|
|
}
|
|
}
|
|
|
|
// Represents the overall state that is required for decoding a
|
|
// AWS Signature V4 chunked reader.
|
|
type s3ChunkedReader struct {
|
|
cred *Credential
|
|
reader *bufio.Reader
|
|
seedSignature string
|
|
seedDate time.Time
|
|
region string
|
|
service string // Service from credential scope (e.g., "s3", "iam")
|
|
state chunkState
|
|
lastChunk bool
|
|
chunkSignature string // Empty string if unsigned streaming upload.
|
|
checkSumAlgorithm string // Empty string if no checksum algorithm is specified.
|
|
checkSumWriter hash.Hash
|
|
chunkSHA256Writer hash.Hash // Calculates sha256 of chunk data.
|
|
n uint64 // Unread bytes in chunk
|
|
err error
|
|
iam *IdentityAccessManagement
|
|
hasTrailer bool
|
|
}
|
|
|
|
// Read chunk reads the chunk token signature portion.
|
|
func (cr *s3ChunkedReader) readS3ChunkHeader() {
|
|
// Read the first chunk line until CRLF.
|
|
var bytesRead, hexChunkSize, hexChunkSignature []byte
|
|
bytesRead, cr.err = readChunkLine(cr.reader)
|
|
// Parse s3 specific chunk extension and fetch the values.
|
|
hexChunkSize, hexChunkSignature = parseS3ChunkExtension(bytesRead)
|
|
|
|
if cr.err != nil {
|
|
return
|
|
}
|
|
// <hex>;token=value - converts the hex into its uint64 form.
|
|
cr.n, cr.err = parseHexUint(hexChunkSize)
|
|
if cr.err != nil {
|
|
return
|
|
}
|
|
if cr.n == 0 {
|
|
cr.err = io.EOF
|
|
}
|
|
|
|
// Save the incoming chunk signature.
|
|
if hexChunkSignature == nil {
|
|
// We are using unsigned streaming upload.
|
|
cr.chunkSignature = ""
|
|
} else {
|
|
cr.chunkSignature = string(hexChunkSignature)
|
|
}
|
|
}
|
|
|
|
type chunkState int
|
|
|
|
const (
|
|
readChunkHeader chunkState = iota
|
|
readChunkTrailer
|
|
readChunk
|
|
readTrailerChunk
|
|
verifyChunk
|
|
verifyChecksum
|
|
eofChunk
|
|
)
|
|
|
|
func (cs chunkState) String() string {
|
|
stateString := ""
|
|
switch cs {
|
|
case readChunkHeader:
|
|
stateString = "readChunkHeader"
|
|
case readChunkTrailer:
|
|
stateString = "readChunkTrailer"
|
|
case readChunk:
|
|
stateString = "readChunk"
|
|
case readTrailerChunk:
|
|
stateString = "readTrailerChunk"
|
|
case verifyChunk:
|
|
stateString = "verifyChunk"
|
|
case verifyChecksum:
|
|
stateString = "verifyChecksum"
|
|
case eofChunk:
|
|
stateString = "eofChunk"
|
|
|
|
}
|
|
return stateString
|
|
}
|
|
|
|
func (cr *s3ChunkedReader) Close() (err error) {
|
|
return nil
|
|
}
|
|
|
|
// Read - implements `io.Reader`, which transparently decodes
|
|
// the incoming AWS Signature V4 streaming signature.
|
|
func (cr *s3ChunkedReader) Read(buf []byte) (n int, err error) {
|
|
for {
|
|
switch cr.state {
|
|
case readChunkHeader:
|
|
cr.readS3ChunkHeader()
|
|
// If we're at the end of a chunk.
|
|
if cr.n == 0 && cr.err == io.EOF {
|
|
cr.state = readChunkTrailer
|
|
cr.lastChunk = true
|
|
continue
|
|
}
|
|
if cr.err != nil {
|
|
return 0, cr.err
|
|
}
|
|
cr.state = readChunk
|
|
case readChunkTrailer:
|
|
err = peekCRLF(cr.reader)
|
|
isTrailingChunk := cr.n == 0 && cr.lastChunk
|
|
|
|
if !isTrailingChunk {
|
|
// If we're not in the trailing chunk, we should consume the bytes no matter what.
|
|
// The error returned by peekCRLF is the same as the one by readCRLF.
|
|
readCRLF(cr.reader)
|
|
cr.err = err
|
|
} else if err != nil && err != errMalformedEncoding {
|
|
cr.err = err
|
|
return 0, errMalformedEncoding
|
|
} else { // equivalent to isTrailingChunk && err == errMalformedEncoding
|
|
// FIXME: The "right" structure of the last chunk as provided by the examples in the
|
|
// AWS documentation is "0\r\n\r\n" instead of "0\r\n", but some s3 clients when calling with
|
|
// streaming-unsigned-payload-trailer omit the last CRLF. To avoid returning an error that, we need to accept both.
|
|
// We arrive here when we're at the end of the 0-byte chunk, depending on the client implementation
|
|
// the client may or may not send the optional CRLF after the 0-byte chunk.
|
|
// If the client sends the optional CRLF, we should consume it.
|
|
if err == nil {
|
|
readCRLF(cr.reader)
|
|
}
|
|
}
|
|
|
|
// If we're using unsigned streaming upload, there is no signature to verify at each chunk.
|
|
if cr.lastChunk && cr.hasTrailer {
|
|
cr.state = readTrailerChunk
|
|
} else if cr.chunkSignature != "" {
|
|
cr.state = verifyChunk
|
|
} else {
|
|
cr.state = readChunkHeader
|
|
}
|
|
|
|
case readTrailerChunk:
|
|
// When using unsigned upload, this would be the raw contents of the trailer chunk:
|
|
//
|
|
// x-amz-checksum-crc32:YABb/g==\n\r\n\r\n // Trailer chunk (note optional \n character)
|
|
// \r\n // CRLF
|
|
//
|
|
// When using signed upload with an additional checksum algorithm, this would be the raw contents of the trailer chunk:
|
|
//
|
|
// x-amz-checksum-crc32:YABb/g==\n\r\n // Trailer chunk (note optional \n character)
|
|
// trailer-signature\r\n
|
|
// \r\n // CRLF
|
|
//
|
|
// This implementation currently only supports the first case.
|
|
// TODO: Implement the second case (signed upload with additional checksum computation for each chunk)
|
|
|
|
extractedCheckSumAlgorithm, extractedChecksum, err := parseChunkChecksum(cr.reader)
|
|
if err != nil {
|
|
cr.err = err
|
|
return 0, err
|
|
}
|
|
|
|
if extractedCheckSumAlgorithm.String() != cr.checkSumAlgorithm {
|
|
errorMessage := fmt.Sprintf("checksum algorithm in trailer '%s' does not match the one advertised in the header '%s'", extractedCheckSumAlgorithm.String(), cr.checkSumAlgorithm)
|
|
glog.V(3).Info(errorMessage)
|
|
cr.err = errors.New(s3err.ErrMsgChecksumAlgorithmMismatch)
|
|
return 0, cr.err
|
|
}
|
|
|
|
// Validate checksum for data integrity (required for both signed and unsigned streaming with trailers)
|
|
computedChecksum := cr.checkSumWriter.Sum(nil)
|
|
base64Checksum := base64.StdEncoding.EncodeToString(computedChecksum)
|
|
if string(extractedChecksum) != base64Checksum {
|
|
glog.V(3).Infof("payload checksum '%s' does not match provided checksum '%s'", base64Checksum, string(extractedChecksum))
|
|
cr.err = errors.New(s3err.ErrMsgPayloadChecksumMismatch)
|
|
return 0, cr.err
|
|
}
|
|
|
|
// TODO: Extract signature from trailer chunk and verify it.
|
|
// For now, we just read the trailer chunk and discard it.
|
|
|
|
cr.state = eofChunk
|
|
|
|
case readChunk:
|
|
// There is no more space left in the request buffer.
|
|
if len(buf) == 0 {
|
|
return n, nil
|
|
}
|
|
rbuf := buf
|
|
// The request buffer is larger than the current chunk size.
|
|
// Read only the current chunk from the underlying reader.
|
|
if uint64(len(rbuf)) > cr.n {
|
|
rbuf = rbuf[:cr.n]
|
|
}
|
|
var n0 int
|
|
n0, cr.err = cr.reader.Read(rbuf)
|
|
if cr.err != nil {
|
|
// We have lesser than chunk size advertised in chunkHeader, this is 'unexpected'.
|
|
if cr.err == io.EOF {
|
|
cr.err = io.ErrUnexpectedEOF
|
|
}
|
|
return 0, cr.err
|
|
}
|
|
|
|
// Calculate sha256.
|
|
cr.chunkSHA256Writer.Write(rbuf[:n0])
|
|
|
|
// Compute checksum
|
|
if cr.checkSumWriter != nil {
|
|
cr.checkSumWriter.Write(rbuf[:n0])
|
|
}
|
|
|
|
// Update the bytes read into request buffer so far.
|
|
n += n0
|
|
buf = buf[n0:]
|
|
// Update bytes to be read of the current chunk before verifying chunk's signature.
|
|
cr.n -= uint64(n0)
|
|
|
|
// If we're at the end of a chunk.
|
|
if cr.n == 0 {
|
|
cr.state = readChunkTrailer
|
|
}
|
|
case verifyChunk:
|
|
// Check if we have credentials for signature verification
|
|
// This handles the case where we have unsigned streaming (no cred) but chunks contain signatures
|
|
//
|
|
// BUG FIX for GitHub issue #6847:
|
|
// Some AWS SDK versions (Java 3.7.412+, .NET 4.0.0-preview.6+) send mixed format:
|
|
// - HTTP headers indicate unsigned streaming (STREAMING-UNSIGNED-PAYLOAD-TRAILER)
|
|
// - But chunk data contains chunk-signature headers (normally only for signed streaming)
|
|
// This causes a nil pointer dereference when trying to verify signatures without credentials
|
|
if cr.cred != nil {
|
|
// Normal signed streaming - verify the chunk signature
|
|
// Calculate the hashed chunk.
|
|
hashedChunk := hex.EncodeToString(cr.chunkSHA256Writer.Sum(nil))
|
|
// Calculate the chunk signature.
|
|
newSignature := cr.getChunkSignature(hashedChunk)
|
|
if !compareSignatureV4(cr.chunkSignature, newSignature) {
|
|
// Chunk signature doesn't match we return signature does not match.
|
|
cr.err = errors.New(s3err.ErrMsgChunkSignatureMismatch)
|
|
return 0, cr.err
|
|
}
|
|
// Newly calculated signature becomes the seed for the next chunk
|
|
// this follows the chaining.
|
|
cr.seedSignature = newSignature
|
|
} else {
|
|
// For unsigned streaming, we should not verify chunk signatures even if they are present
|
|
// This fixes the bug where AWS SDKs send chunk signatures with unsigned streaming headers
|
|
glog.V(3).Infof("Skipping chunk signature verification for unsigned streaming")
|
|
}
|
|
|
|
// Common cleanup and state transition for both signed and unsigned streaming
|
|
cr.chunkSHA256Writer.Reset()
|
|
if cr.lastChunk {
|
|
cr.state = eofChunk
|
|
} else {
|
|
cr.state = readChunkHeader
|
|
}
|
|
case eofChunk:
|
|
return n, io.EOF
|
|
}
|
|
}
|
|
}
|
|
|
|
// getChunkSignature - get chunk signature.
|
|
func (cr *s3ChunkedReader) getChunkSignature(hashedChunk string) string {
|
|
// Calculate string to sign.
|
|
stringToSign := signV4Algorithm + "-PAYLOAD" + "\n" +
|
|
cr.seedDate.Format(iso8601Format) + "\n" +
|
|
getScope(cr.seedDate, cr.region, cr.service) + "\n" +
|
|
cr.seedSignature + "\n" +
|
|
emptySHA256 + "\n" +
|
|
hashedChunk
|
|
|
|
// Get hmac signing key.
|
|
signingKey := getSigningKey(cr.cred.SecretKey, cr.seedDate.Format(yyyymmdd), cr.region, cr.service)
|
|
|
|
// Calculate and return signature.
|
|
return getSignature(signingKey, stringToSign)
|
|
}
|
|
|
|
func readCRLF(reader *bufio.Reader) error {
|
|
buf := make([]byte, 2)
|
|
_, err := io.ReadFull(reader, buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return checkCRLF(buf)
|
|
}
|
|
|
|
func peekCRLF(reader *bufio.Reader) error {
|
|
buf, err := reader.Peek(2)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := checkCRLF(buf); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkCRLF(buf []byte) error {
|
|
if len(buf) != 2 || buf[0] != '\r' || buf[1] != '\n' {
|
|
return errMalformedEncoding
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func readChunkLine(b *bufio.Reader) ([]byte, error) {
|
|
buf, err := b.ReadSlice('\n')
|
|
if err != nil {
|
|
// We always know when EOF is coming.
|
|
// If the caller asked for a line, there should be a line.
|
|
switch err {
|
|
case io.EOF:
|
|
err = io.ErrUnexpectedEOF
|
|
case bufio.ErrBufferFull:
|
|
err = errLineTooLong
|
|
}
|
|
return nil, err
|
|
}
|
|
if len(buf) >= maxLineLength {
|
|
return nil, errLineTooLong
|
|
}
|
|
return trimTrailingWhitespace(buf), nil
|
|
}
|
|
|
|
// trimTrailingWhitespace - trim trailing white space.
|
|
func trimTrailingWhitespace(b []byte) []byte {
|
|
for len(b) > 0 && isASCIISpace(b[len(b)-1]) {
|
|
b = b[:len(b)-1]
|
|
}
|
|
return b
|
|
}
|
|
|
|
// isASCIISpace - is ascii space?
|
|
func isASCIISpace(b byte) bool {
|
|
return b == ' ' || b == '\t' || b == '\n' || b == '\r'
|
|
}
|
|
|
|
// Constant s3 chunk encoding signature.
|
|
const s3ChunkSignatureStr = ";chunk-signature="
|
|
|
|
// parseS3ChunkExtension removes any s3 specific chunk-extension from buf.
|
|
// For example,
|
|
//
|
|
// "10000;chunk-signature=..." => "10000", "chunk-signature=..."
|
|
func parseS3ChunkExtension(buf []byte) ([]byte, []byte) {
|
|
buf = trimTrailingWhitespace(buf)
|
|
semi := bytes.Index(buf, []byte(s3ChunkSignatureStr))
|
|
// Chunk signature not found, return the whole buffer.
|
|
// This means we're using unsigned streaming upload.
|
|
if semi == -1 {
|
|
return buf, nil
|
|
}
|
|
return buf[:semi], parseChunkSignature(buf[semi:])
|
|
}
|
|
|
|
func parseChunkChecksum(b *bufio.Reader) (ChecksumAlgorithm, []byte, error) {
|
|
// Read trailer lines until empty line
|
|
var checksumAlgorithm ChecksumAlgorithm
|
|
var checksum []byte
|
|
|
|
for {
|
|
bytesRead, err := readChunkLine(b)
|
|
if err != nil {
|
|
return ChecksumAlgorithmNone, nil, err
|
|
}
|
|
|
|
if len(bytesRead) == 0 {
|
|
break
|
|
}
|
|
|
|
parts := bytes.SplitN(bytesRead, []byte(":"), 2)
|
|
if len(parts) == 2 {
|
|
key := string(bytes.TrimSpace(parts[0]))
|
|
value := bytes.TrimSpace(parts[1])
|
|
if alg, err := extractChecksumAlgorithm(key); err == nil {
|
|
if checksumAlgorithm != ChecksumAlgorithmNone {
|
|
glog.V(3).Infof("multiple checksum headers found in trailer, using last: %s", key)
|
|
}
|
|
checksumAlgorithm = alg
|
|
checksum = bytes.Clone(value)
|
|
}
|
|
// Ignore other trailer headers like x-amz-trailer-signature
|
|
}
|
|
}
|
|
|
|
return checksumAlgorithm, checksum, nil
|
|
}
|
|
|
|
func parseChunkSignature(chunk []byte) []byte {
|
|
chunkSplits := bytes.SplitN(chunk, []byte("="), 2)
|
|
return chunkSplits[1] // Keep only the signature.
|
|
}
|
|
|
|
func parseHexUint(v []byte) (n uint64, err error) {
|
|
for i, b := range v {
|
|
switch {
|
|
case '0' <= b && b <= '9':
|
|
b = b - '0'
|
|
case 'a' <= b && b <= 'f':
|
|
b = b - 'a' + 10
|
|
case 'A' <= b && b <= 'F':
|
|
b = b - 'A' + 10
|
|
default:
|
|
return 0, errors.New("invalid byte in chunk length")
|
|
}
|
|
if i == 16 {
|
|
return 0, errors.New("http chunk length too large")
|
|
}
|
|
n <<= 4
|
|
n |= uint64(b)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Checksum Algorithm represents the various checksum algorithms supported.
|
|
type ChecksumAlgorithm int
|
|
|
|
const (
|
|
ChecksumAlgorithmNone ChecksumAlgorithm = iota
|
|
ChecksumAlgorithmCRC32
|
|
ChecksumAlgorithmCRC32C
|
|
ChecksumAlgorithmCRC64NVMe
|
|
ChecksumAlgorithmSHA1
|
|
ChecksumAlgorithmSHA256
|
|
)
|
|
|
|
func (ca ChecksumAlgorithm) String() string {
|
|
switch ca {
|
|
case ChecksumAlgorithmNone:
|
|
return ""
|
|
case ChecksumAlgorithmCRC32:
|
|
return "x-amz-checksum-crc32"
|
|
case ChecksumAlgorithmCRC32C:
|
|
return "x-amz-checksum-crc32c"
|
|
case ChecksumAlgorithmCRC64NVMe:
|
|
return "x-amz-checksum-crc64nvme"
|
|
case ChecksumAlgorithmSHA1:
|
|
return "x-amz-checksum-sha1"
|
|
case ChecksumAlgorithmSHA256:
|
|
return "x-amz-checksum-sha256"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// getCheckSumWriter - get checksum writer.
|
|
func getCheckSumWriter(checksumAlgorithm ChecksumAlgorithm) hash.Hash {
|
|
switch checksumAlgorithm {
|
|
case ChecksumAlgorithmCRC32:
|
|
return crc32.NewIEEE()
|
|
case ChecksumAlgorithmCRC32C:
|
|
return crc32.New(crc32.MakeTable(crc32.Castagnoli))
|
|
case ChecksumAlgorithmCRC64NVMe:
|
|
return crc64nvme.New()
|
|
case ChecksumAlgorithmSHA1:
|
|
return sha1.New()
|
|
case ChecksumAlgorithmSHA256:
|
|
return sha256.New()
|
|
}
|
|
return nil
|
|
}
|