v3/internal/object_metadata.go (105 lines of code) (raw):

// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package internal import ( "encoding/base64" "encoding/json" "fmt" "github.com/aws/amazon-s3-encryption-client-go/v3/materials" "strconv" ) // DefaultInstructionKeySuffix is appended to the end of the instruction file key when // grabbing or saving to S3 const DefaultInstructionKeySuffix = ".instruction" const ( metaHeader = "x-amz-meta" keyV1Header = "x-amz-key" keyV2Header = keyV1Header + "-v2" ivHeader = "x-amz-iv" matDescHeader = "x-amz-matdesc" CekAlgorithmHeader = "x-amz-cek-alg" KeyringAlgorithmHeader = "x-amz-wrap-alg" tagLengthHeader = "x-amz-tag-len" unencryptedContentLengthHeader = "x-amz-unencrypted-content-length" ) // ObjectMetadata encryption starts off by generating a random symmetric key using // AES GCM. The SDK generates a random IV based off the encryption cipher // chosen. The master key that was provided, whether by the user or KMS, will be used // to encrypt the randomly generated symmetric key and base64 encode the iv. This will // allow for decryption of that same data later. type ObjectMetadata struct { // IV is the randomly generated IV base64 encoded. IV string `json:"x-amz-iv"` // CipherKey is the randomly generated cipher key. CipherKey string `json:"x-amz-key-v2"` // MaterialDesc is a description to distinguish from other envelopes. MatDesc string `json:"x-amz-matdesc"` KeyringAlg string `json:"x-amz-wrap-alg"` CEKAlg string `json:"x-amz-cek-alg"` TagLen string `json:"x-amz-tag-len"` UnencryptedContentLen string `json:"x-amz-unencrypted-content-length"` } func (e *ObjectMetadata) GetDecodedKey() ([]byte, error) { key, err := base64.StdEncoding.DecodeString(e.CipherKey) if err != nil { return nil, err } return key, err } func (e *ObjectMetadata) GetDecodedIV() ([]byte, error) { iv, err := base64.StdEncoding.DecodeString(e.IV) if err != nil { return nil, err } return iv, err } func (e *ObjectMetadata) GetMatDesc() (string, error) { return e.MatDesc, nil } // UnmarshalJSON unmarshalls the given JSON bytes into ObjectMetadata func (e *ObjectMetadata) UnmarshalJSON(value []byte) error { type StrictEnvelope ObjectMetadata type LaxEnvelope struct { StrictEnvelope TagLen json.RawMessage `json:"x-amz-tag-len"` UnencryptedContentLen json.RawMessage `json:"x-amz-unencrypted-content-length"` } inner := LaxEnvelope{} err := json.Unmarshal(value, &inner) if err != nil { return err } *e = ObjectMetadata(inner.StrictEnvelope) e.TagLen, err = getJSONNumberAsString(inner.TagLen) if err != nil { return fmt.Errorf("failed to parse tag length: %w", err) } e.UnencryptedContentLen, err = getJSONNumberAsString(inner.UnencryptedContentLen) if err != nil { return fmt.Errorf("failed to parse unencrypted content length: %w", err) } return nil } // getJSONNumberAsString will attempt to convert the provided bytes into a string representation of a JSON Number. // Only supports byte values that are string or integers, not floats. If the provided value is JSON Null, empty string // will be returned. func getJSONNumberAsString(data []byte) (string, error) { if len(data) == 0 { return "", nil } // first try string, this also catches null value var s *string err := json.Unmarshal(data, &s) if err == nil && s != nil { return *s, nil } else if err == nil { return "", nil } // fallback to int64 var i int64 err = json.Unmarshal(data, &i) if err == nil { return strconv.FormatInt(i, 10), nil } return "", fmt.Errorf("failed to parse as JSON Number") } func EncodeMeta(reader lengthReader, cryptographicMaterials materials.CryptographicMaterials) (ObjectMetadata, error) { iv := base64.StdEncoding.EncodeToString(cryptographicMaterials.IV) key := base64.StdEncoding.EncodeToString(cryptographicMaterials.EncryptedKey) encodedMatDesc, err := cryptographicMaterials.MaterialDescription.EncodeDescription() if err != nil { return ObjectMetadata{}, err } contentLength := reader.GetContentLength() return ObjectMetadata{ CipherKey: key, IV: iv, MatDesc: string(encodedMatDesc), KeyringAlg: cryptographicMaterials.KeyringAlgorithm, CEKAlg: cryptographicMaterials.CEKAlgorithm, TagLen: cryptographicMaterials.TagLength, UnencryptedContentLen: strconv.FormatInt(contentLength, 10), }, nil }