Skip to content

Commit

Permalink
api: Support encryption for Put/Get Object (#613)
Browse files Browse the repository at this point in the history
  • Loading branch information
vadmeste authored and harshavardhana committed Mar 15, 2017
1 parent eaa1401 commit 5857456
Show file tree
Hide file tree
Showing 10 changed files with 1,074 additions and 15 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ The full API Reference is available here.
* [`RemoveObjects`](https://docs.minio.io/docs/golang-client-api-reference#RemoveObjects)
* [`RemoveIncompleteUpload`](https://docs.minio.io/docs/golang-client-api-reference#RemoveIncompleteUpload)
### API Reference: Encrypted Object Operations
* [`GetEncryptedObject`](https://docs.minio.io/docs/golang-client-api-reference#GetEncryptedObject)
* [`PutEncryptedObject`](https://docs.minio.io/docs/golang-client-api-reference#PutEncryptedObject)
### API Reference : Presigned Operations
* [`PresignedGetObject`](https://docs.minio.io/docs/golang-client-api-reference#PresignedGetObject)
Expand Down Expand Up @@ -238,6 +243,11 @@ The full API Reference is available here.
* [removeincompleteupload.go](https://github.com/minio/minio-go/blob/master/examples/s3/removeincompleteupload.go)
* [removeobjects.go](https://github.com/minio/minio-go/blob/master/examples/s3/removeobjects.go)
#### Full Examples : Encrypted Object Operations
* [put-encrypted-object.go](https://github.com/minio/minio-go/blob/master/examples/s3/put-encrypted-object.go)
* [get-encrypted-object.go](https://github.com/minio/minio-go/blob/master/examples/s3/get-encrypted-object.go)
#### Full Examples : Presigned Operations
* [presignedgetobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/presignedgetobject.go)
* [presignedputobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/presignedputobject.go)
Expand Down
23 changes: 23 additions & 0 deletions api-get-object.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,29 @@ import (
"time"
)

// GetEncryptedObject deciphers and streams data stored in the server after applying a specifed encryption materiels
func (c Client) GetEncryptedObject(bucketName, objectName string, encryptMaterials EncryptionMaterials) (io.Reader, error) {

if encryptMaterials == nil {
return nil, ErrInvalidArgument("Unable to recognize empty encryption properties")
}

// Fetch encrypted object
encReader, err := c.GetObject(bucketName, objectName)
if err != nil {
return nil, err
}
// Stat object to get its encryption metadata
st, err := encReader.Stat()
if err != nil {
return nil, err
}

encryptMaterials.SetupDecryptMode(encReader, st.Metadata)

return encryptMaterials, nil
}

// GetObject - returns an seekable, readable object.
func (c Client) GetObject(bucketName, objectName string) (*Object, error) {
// Input validation.
Expand Down
21 changes: 21 additions & 0 deletions api-put-object-progress.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,27 @@ func (c Client) PutObjectWithProgress(bucketName, objectName string, reader io.R
return c.PutObjectWithMetadata(bucketName, objectName, reader, metaData, progress)
}

// PutEncryptedObject - Encrypt and store object.
func (c Client) PutEncryptedObject(bucketName, objectName string, reader io.Reader, encryptMaterials EncryptionMaterials, metaData map[string][]string, progress io.Reader) (n int64, err error) {

if encryptMaterials == nil {
return 0, ErrInvalidArgument("Unable to recognize empty encryption properties")
}

if err := encryptMaterials.SetupEncryptMode(reader); err != nil {
return 0, err
}

if metaData == nil {
metaData = make(map[string][]string)
}
for k, v := range encryptMaterials.GetHeaders() {
metaData[k] = v
}

return c.PutObjectWithMetadata(bucketName, objectName, encryptMaterials, metaData, progress)
}

// PutObjectWithMetadata - with metadata.
func (c Client) PutObjectWithMetadata(bucketName, objectName string, reader io.Reader, metaData map[string][]string, progress io.Reader) (n int64, err error) {
// Input validation.
Expand Down
162 changes: 162 additions & 0 deletions api_functional_v4_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package minio
import (
"bytes"
crand "crypto/rand"
"encoding/hex"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -1791,6 +1792,167 @@ func TestCopyObject(t *testing.T) {
}
}

// TestEncryptionPutGet tests client side encryption
func TestEncryptionPutGet(t *testing.T) {
if testing.Short() {
t.Skip("skipping functional tests for the short runs")
}

// Seed random based on current time.
rand.Seed(time.Now().Unix())

// Instantiate new minio client object.
c, err := New(
os.Getenv("S3_ADDRESS"),
os.Getenv("ACCESS_KEY"),
os.Getenv("SECRET_KEY"),
mustParseBool(os.Getenv("S3_SECURE")),
)
if err != nil {
t.Fatal("Error:", err)
}

// Enable tracing, write to stderr.
// c.TraceOn(os.Stderr)

// Set user agent.
c.SetAppInfo("Minio-go-FunctionalTest", "0.1.0")

// Generate a new random bucket name.
bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test")

// Make a new bucket.
err = c.MakeBucket(bucketName, "us-east-1")
if err != nil {
t.Fatal("Error:", err, bucketName)
}

// Generate a symmetric key
symKey := NewSymmetricKey([]byte("my-secret-key-00"))

// Generate an assymmetric key from predefine public and private certificates
privateKey, err := hex.DecodeString(
"30820277020100300d06092a864886f70d0101010500048202613082025d" +
"0201000281810087b42ea73243a3576dc4c0b6fa245d339582dfdbddc20c" +
"bb8ab666385034d997210c54ba79275c51162a1221c3fb1a4c7c61131ca6" +
"5563b319d83474ef5e803fbfa7e52b889e1893b02586b724250de7ac6351" +
"cc0b7c638c980acec0a07020a78eed7eaa471eca4b92071394e061346c06" +
"15ccce2f465dee2080a89e43f29b5702030100010281801dd5770c3af8b3" +
"c85cd18cacad81a11bde1acfac3eac92b00866e142301fee565365aa9af4" +
"57baebf8bb7711054d071319a51dd6869aef3848ce477a0dc5f0dbc0c336" +
"5814b24c820491ae2bb3c707229a654427e03307fec683e6b27856688f08" +
"bdaa88054c5eeeb773793ff7543ee0fb0e2ad716856f2777f809ef7e6fa4" +
"41024100ca6b1edf89e8a8f93cce4b98c76c6990a09eb0d32ad9d3d04fbf" +
"0b026fa935c44f0a1c05dd96df192143b7bda8b110ec8ace28927181fd8c" +
"d2f17330b9b63535024100aba0260afb41489451baaeba423bee39bcbd1e" +
"f63dd44ee2d466d2453e683bf46d019a8baead3a2c7fca987988eb4d565e" +
"27d6be34605953f5034e4faeec9bdb0241009db2cb00b8be8c36710aff96" +
"6d77a6dec86419baca9d9e09a2b761ea69f7d82db2ae5b9aae4246599bb2" +
"d849684d5ab40e8802cfe4a2b358ad56f2b939561d2902404e0ead9ecafd" +
"bb33f22414fa13cbcc22a86bdf9c212ce1a01af894e3f76952f36d6c904c" +
"bd6a7e0de52550c9ddf31f1e8bfe5495f79e66a25fca5c20b3af5b870241" +
"0083456232aa58a8c45e5b110494599bda8dbe6a094683a0539ddd24e19d" +
"47684263bbe285ad953d725942d670b8f290d50c0bca3d1dc9688569f1d5" +
"9945cb5c7d")

if err != nil {
t.Fatal(err)
}

publicKey, err := hex.DecodeString("30819f300d06092a864886f70d010101050003818d003081890281810087" +
"b42ea73243a3576dc4c0b6fa245d339582dfdbddc20cbb8ab666385034d9" +
"97210c54ba79275c51162a1221c3fb1a4c7c61131ca65563b319d83474ef" +
"5e803fbfa7e52b889e1893b02586b724250de7ac6351cc0b7c638c980ace" +
"c0a07020a78eed7eaa471eca4b92071394e061346c0615ccce2f465dee20" +
"80a89e43f29b570203010001")
if err != nil {
t.Fatal(err)
}

// Generate an asymmetric key
asymKey, err := NewAsymmetricKey(privateKey, publicKey)
if err != nil {
t.Fatal(err)
}

// Object custom metadata
customContentType := "custom/contenttype"

testCases := []struct {
buf []byte
encKey EncryptionKey
}{
{encKey: symKey, buf: bytes.Repeat([]byte("F"), 0)},
{encKey: symKey, buf: bytes.Repeat([]byte("F"), 1)},
{encKey: symKey, buf: bytes.Repeat([]byte("F"), 15)},
{encKey: symKey, buf: bytes.Repeat([]byte("F"), 16)},
{encKey: symKey, buf: bytes.Repeat([]byte("F"), 17)},
{encKey: symKey, buf: bytes.Repeat([]byte("F"), 31)},
{encKey: symKey, buf: bytes.Repeat([]byte("F"), 32)},
{encKey: symKey, buf: bytes.Repeat([]byte("F"), 33)},
{encKey: symKey, buf: bytes.Repeat([]byte("F"), 1024)},
{encKey: symKey, buf: bytes.Repeat([]byte("F"), 1024*2)},
{encKey: symKey, buf: bytes.Repeat([]byte("F"), 1024*1024)},

{encKey: asymKey, buf: bytes.Repeat([]byte("F"), 0)},
{encKey: asymKey, buf: bytes.Repeat([]byte("F"), 1)},
{encKey: asymKey, buf: bytes.Repeat([]byte("F"), 16)},
{encKey: asymKey, buf: bytes.Repeat([]byte("F"), 32)},
{encKey: asymKey, buf: bytes.Repeat([]byte("F"), 1024)},
{encKey: asymKey, buf: bytes.Repeat([]byte("F"), 1024*1024)},
}

for i, testCase := range testCases {
// Generate a random object name
objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")

// Secured object
cbcMaterials, err := NewCBCSecureMaterials(testCase.encKey)
if err != nil {
t.Fatal(err)
}

// Put encrypted data
_, err = c.PutEncryptedObject(bucketName, objectName, bytes.NewReader(testCase.buf), cbcMaterials, map[string][]string{"Content-Type": []string{customContentType}}, nil)
if err != nil {
t.Fatalf("Test %d, error: %v %v %v", i+1, err, bucketName, objectName)
}

// Read the data back
r, err := c.GetEncryptedObject(bucketName, objectName, cbcMaterials)
if err != nil {
t.Fatalf("Test %d, error: %v %v %v", i+1, err, bucketName, objectName)
}

// Compare the sent object with the received one
recvBuffer := bytes.NewBuffer([]byte{})
if _, err = io.Copy(recvBuffer, r); err != nil {
t.Fatalf("Test %d, error: %v", i+1, err)
}
if recvBuffer.Len() != len(testCase.buf) {
t.Fatalf("Test %d, error: number of bytes of received object does not match, want %v, got %v\n",
i+1, len(testCase.buf), recvBuffer.Len())
}
if !bytes.Equal(testCase.buf, recvBuffer.Bytes()) {
t.Fatalf("Test %d, error: Encrypted sent is not equal to decrypted, want `%x`, go `%x`", i+1, testCase.buf, recvBuffer.Bytes())
}

// Remove test object
err = c.RemoveObject(bucketName, objectName)
if err != nil {
t.Fatalf("Test %d, error: %v", i+1, err)
}

}

// Remove test bucket
err = c.RemoveBucket(bucketName)
if err != nil {
t.Fatal("Error:", err)
}

}

func TestBucketNotification(t *testing.T) {
if testing.Short() {
t.Skip("skipping functional tests for the short runs")
Expand Down
Loading

0 comments on commit 5857456

Please sign in to comment.