Skip to content

Add support for SHA-256 and SHA-512 digests in temporary URLs #190

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 38 additions & 5 deletions swift.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import (
"crypto/hmac"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
Expand Down Expand Up @@ -1908,21 +1911,51 @@ func (c *Connection) ObjectDelete(ctx context.Context, container string, objectN
return err
}

// ObjectTempUrl returns a temporary URL for an object
// ObjectTempUrl returns a temporary URL for an object using the sha1 hashing function.
//
// The cluster must support the sha1 digest.
func (c *Connection) ObjectTempUrl(container string, objectName string, secretKey string, method string, expires time.Time) string {
sig := c.objectTempUrlSignature(container, objectName, secretKey, method, expires, sha1.New)
s := hex.EncodeToString(sig)

return fmt.Sprintf("%s/%s/%s?temp_url_sig=%s&temp_url_expires=%d", c.StorageUrl, container, objectName, s, expires.Unix())
}

// ObjectTempUrlSha256 returns a temporary URL for an object using the sha256 hashing function.
//
// The cluster must support the sha256 digest.
func (c *Connection) ObjectTempUrlSha256(container string, objectName string, secretKey string, method string, expires time.Time) string {
sig := c.objectTempUrlSignature(container, objectName, secretKey, method, expires, sha256.New)
s := hex.EncodeToString(sig)

return fmt.Sprintf("%s/%s/%s?temp_url_sig=%s&temp_url_expires=%d", c.StorageUrl, container, objectName, s, expires.Unix())
}

// ObjectTempUrlSha512 returns a temporary URL for an object using the sha512 hashing function.
//
// The cluster must support the sha512 digest.
func (c *Connection) ObjectTempUrlSha512(container string, objectName string, secretKey string, method string, expires time.Time) string {
sig := c.objectTempUrlSignature(container, objectName, secretKey, method, expires, sha512.New)
// The sha512 digest must be encoded using base64.
s := base64.RawURLEncoding.EncodeToString(sig)

return fmt.Sprintf("%s/%s/%s?temp_url_sig=sha512:%s&temp_url_expires=%d", c.StorageUrl, container, objectName, s, expires.Unix())
}

// objectTempUrlSignature returns a signature for an object using the provided hash function.
func (c *Connection) objectTempUrlSignature(container string, objectName string, secretKey string, method string, expires time.Time, hash func() hash.Hash) []byte {
c.authLock.Lock()
storageUrl := c.StorageUrl
c.authLock.Unlock()
if storageUrl == "" {
return "" // Cannot do better without changing the interface
return []byte{} // Cannot do better without changing the interface
}

mac := hmac.New(sha1.New, []byte(secretKey))
mac := hmac.New(hash, []byte(secretKey))
prefix, _ := url.Parse(storageUrl)
body := fmt.Sprintf("%s\n%d\n%s/%s/%s", method, expires.Unix(), prefix.Path, container, objectName)
mac.Write([]byte(body))
sig := hex.EncodeToString(mac.Sum(nil))
return fmt.Sprintf("%s/%s/%s?temp_url_sig=%s&temp_url_expires=%d", c.StorageUrl, container, objectName, sig, expires.Unix())
return mac.Sum(nil)
}

// parseResponseStatus parses string like "200 OK" and returns Error.
Expand Down
124 changes: 124 additions & 0 deletions swift_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2210,6 +2210,130 @@ func TestTempUrl(t *testing.T) {
}
}

func TestTempUrlSha256(t *testing.T) {
ctx := context.Background()
c, rollback := makeConnectionWithContainer(t)
defer rollback()
err := c.ObjectPutBytes(ctx, CONTAINER, OBJECT, []byte(CONTENTS), "")
if err != nil {
t.Fatal(err)
}
defer func() {
err = c.ObjectDelete(ctx, CONTAINER, OBJECT)
if err != nil {
t.Fatal(err)
}
}()

m := swift.Metadata{}
m["temp-url-key"] = SECRET_KEY
err = c.AccountUpdate(ctx, m.AccountHeaders())
if err != nil {
t.Fatal(err)
}

expiresTime := time.Now().Add(20 * time.Minute)
tempUrl := c.ObjectTempUrlSha256(CONTAINER, OBJECT, SECRET_KEY, "GET", expiresTime)

resp, err := http.Get(tempUrl)
if err != nil {
t.Fatal("Failed to retrieve file from temporary url")
}
defer func() {
err := resp.Body.Close()
if err != nil {
t.Error("Close failed", err)
}
}()

if resp.StatusCode == 401 {
t.Log("Server doesn't support tempurl")
} else if resp.StatusCode != 200 {
t.Fatal("HTTP Error retrieving file from temporary url", resp.StatusCode)
} else {
var content []byte
if content, err = io.ReadAll(resp.Body); err != nil || string(content) != CONTENTS {
t.Error("Bad content", err)
}

resp, err = http.Post(tempUrl, "image/jpeg", bytes.NewReader([]byte(CONTENTS)))
if err != nil {
t.Fatal("Failed to retrieve file from temporary url")
}
defer func() {
err := resp.Body.Close()
if err != nil {
t.Error("Close failed", err)
}
}()
if resp.StatusCode != 401 {
t.Fatal("Expecting server to forbid access to object")
}
}
}

func TestTempUrlSha512(t *testing.T) {
ctx := context.Background()
c, rollback := makeConnectionWithContainer(t)
defer rollback()
err := c.ObjectPutBytes(ctx, CONTAINER, OBJECT, []byte(CONTENTS), "")
if err != nil {
t.Fatal(err)
}
defer func() {
err = c.ObjectDelete(ctx, CONTAINER, OBJECT)
if err != nil {
t.Fatal(err)
}
}()

m := swift.Metadata{}
m["temp-url-key"] = SECRET_KEY
err = c.AccountUpdate(ctx, m.AccountHeaders())
if err != nil {
t.Fatal(err)
}

expiresTime := time.Now().Add(20 * time.Minute)
tempUrl := c.ObjectTempUrlSha256(CONTAINER, OBJECT, SECRET_KEY, "GET", expiresTime)

resp, err := http.Get(tempUrl)
if err != nil {
t.Fatal("Failed to retrieve file from temporary url")
}
defer func() {
err := resp.Body.Close()
if err != nil {
t.Error("Close failed", err)
}
}()

if resp.StatusCode == 401 {
t.Log("Server doesn't support tempurl")
} else if resp.StatusCode != 200 {
t.Fatal("HTTP Error retrieving file from temporary url", resp.StatusCode)
} else {
var content []byte
if content, err = io.ReadAll(resp.Body); err != nil || string(content) != CONTENTS {
t.Error("Bad content", err)
}

resp, err = http.Post(tempUrl, "image/jpeg", bytes.NewReader([]byte(CONTENTS)))
if err != nil {
t.Fatal("Failed to retrieve file from temporary url")
}
defer func() {
err := resp.Body.Close()
if err != nil {
t.Error("Close failed", err)
}
}()
if resp.StatusCode != 401 {
t.Fatal("Expecting server to forbid access to object")
}
}
}

func TestQueryInfo(t *testing.T) {
ctx := context.Background()
c, rollback := makeConnectionAuth(t)
Expand Down