forked from aws/aws-sdk-go
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Event Stream SigV4 Chunk Signinging (aws#2996)
- Loading branch information
Showing
7 changed files
with
387 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package v4 | ||
|
||
import ( | ||
"encoding/hex" | ||
"strings" | ||
"time" | ||
|
||
"github.com/aws/aws-sdk-go/aws/credentials" | ||
) | ||
|
||
type credentialValueProvider interface { | ||
Get() (credentials.Value, error) | ||
} | ||
|
||
// StreamSigner implements signing of event stream encoded payloads | ||
type StreamSigner struct { | ||
region string | ||
service string | ||
|
||
credentials credentialValueProvider | ||
|
||
prevSig []byte | ||
} | ||
|
||
// NewStreamSigner creates a SigV4 signer used to sign Event Stream encoded messages | ||
func NewStreamSigner(region, service string, seedSignature []byte, credentials *credentials.Credentials) *StreamSigner { | ||
return &StreamSigner{ | ||
region: region, | ||
service: service, | ||
credentials: credentials, | ||
prevSig: seedSignature, | ||
} | ||
} | ||
|
||
// GetSignature takes an event stream encoded headers and payload and returns a signature | ||
func (s *StreamSigner) GetSignature(headers, payload []byte, date time.Time) ([]byte, error) { | ||
credValue, err := s.credentials.Get() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
sigKey := deriveSigningKey(s.region, s.service, credValue.SecretAccessKey, date) | ||
|
||
keyPath := buildSigningScope(s.region, s.service, date) | ||
|
||
stringToSign := buildEventStreamStringToSign(headers, payload, s.prevSig, keyPath, date) | ||
|
||
signature := hmacSHA256(sigKey, []byte(stringToSign)) | ||
s.prevSig = signature | ||
|
||
return signature, nil | ||
} | ||
|
||
func buildEventStreamStringToSign(headers, payload, prevSig []byte, scope string, date time.Time) string { | ||
return strings.Join([]string{ | ||
"AWS4-HMAC-SHA256-PAYLOAD", | ||
formatTime(date), | ||
scope, | ||
hex.EncodeToString(prevSig), | ||
hex.EncodeToString(hashSHA256(headers)), | ||
hex.EncodeToString(hashSHA256(payload)), | ||
}, "\n") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
// +build go1.7 | ||
|
||
package v4 | ||
|
||
import ( | ||
"encoding/hex" | ||
"fmt" | ||
"strings" | ||
"testing" | ||
"time" | ||
|
||
"github.com/aws/aws-sdk-go/aws/credentials" | ||
) | ||
|
||
type periodicBadCredentials struct { | ||
call int | ||
credentials *credentials.Credentials | ||
} | ||
|
||
func (p *periodicBadCredentials) Get() (credentials.Value, error) { | ||
defer func() { | ||
p.call++ | ||
}() | ||
|
||
if p.call%2 == 0 { | ||
return credentials.Value{}, fmt.Errorf("credentials error") | ||
} | ||
|
||
return p.credentials.Get() | ||
} | ||
|
||
type chunk struct { | ||
headers, payload []byte | ||
} | ||
|
||
func mustDecodeHex(b []byte, err error) []byte { | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
return b | ||
} | ||
|
||
func TestStreamingChunkSigner(t *testing.T) { | ||
const ( | ||
region = "us-east-1" | ||
service = "transcribe" | ||
seedSignature = "9d9ab996c81f32c9d4e6fc166c92584f3741d1cb5ce325cd11a77d1f962c8de2" | ||
) | ||
|
||
staticCredentials := credentials.NewStaticCredentials("AKIDEXAMPLE", "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY", "") | ||
currentTime := time.Date(2019, 1, 27, 22, 37, 54, 0, time.UTC) | ||
|
||
cases := map[string]struct { | ||
credentials credentialValueProvider | ||
chunks []chunk | ||
expectedSignatures map[int]string | ||
expectedErrors map[int]string | ||
}{ | ||
"signature calculation": { | ||
credentials: staticCredentials, | ||
chunks: []chunk{ | ||
{headers: []byte("headers"), payload: []byte("payload")}, | ||
{headers: []byte("more headers"), payload: []byte("more payload")}, | ||
}, | ||
expectedSignatures: map[int]string{ | ||
0: "681a7eaa82891536f24af7ec7e9219ee251ccd9bac2f1b981eab7c5ec8579115", | ||
1: "07633d9d4ab4d81634a2164934d1f648c7cbc6839a8cf0773d818127a267e4d6", | ||
}, | ||
}, | ||
"signature calculation errors": { | ||
credentials: &periodicBadCredentials{credentials: staticCredentials}, | ||
chunks: []chunk{ | ||
{headers: []byte("headers"), payload: []byte("payload")}, | ||
{headers: []byte("headers"), payload: []byte("payload")}, | ||
{headers: []byte("more headers"), payload: []byte("more payload")}, | ||
{headers: []byte("more headers"), payload: []byte("more payload")}, | ||
}, | ||
expectedSignatures: map[int]string{ | ||
1: "681a7eaa82891536f24af7ec7e9219ee251ccd9bac2f1b981eab7c5ec8579115", | ||
3: "07633d9d4ab4d81634a2164934d1f648c7cbc6839a8cf0773d818127a267e4d6", | ||
}, | ||
expectedErrors: map[int]string{ | ||
0: "credentials error", | ||
2: "credentials error", | ||
}, | ||
}, | ||
} | ||
|
||
for name, tt := range cases { | ||
t.Run(name, func(t *testing.T) { | ||
chunkSigner := &StreamSigner{ | ||
region: region, | ||
service: service, | ||
credentials: tt.credentials, | ||
prevSig: mustDecodeHex(hex.DecodeString(seedSignature)), | ||
} | ||
|
||
for i, chunk := range tt.chunks { | ||
var expectedError string | ||
if len(tt.expectedErrors) != 0 { | ||
_, ok := tt.expectedErrors[i] | ||
if ok { | ||
expectedError = tt.expectedErrors[i] | ||
} | ||
} | ||
|
||
signature, err := chunkSigner.GetSignature(chunk.headers, chunk.payload, currentTime) | ||
if err == nil && len(expectedError) > 0 { | ||
t.Errorf("expected error, but got nil") | ||
continue | ||
} else if err != nil && len(expectedError) == 0 { | ||
t.Errorf("expected no error, but got %v", err) | ||
continue | ||
} else if err != nil && len(expectedError) > 0 && !strings.Contains(err.Error(), expectedError) { | ||
t.Errorf("expected %v, but got %v", expectedError, err) | ||
continue | ||
} else if len(expectedError) > 0 { | ||
continue | ||
} | ||
|
||
expectedSignature, ok := tt.expectedSignatures[i] | ||
if !ok { | ||
t.Fatalf("expected signature not provided for test case") | ||
} | ||
|
||
if e, a := expectedSignature, hex.EncodeToString(signature); e != a { | ||
t.Errorf("expected %v, got %v", e, a) | ||
} | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package eventstreamapi | ||
|
||
import ( | ||
"bytes" | ||
"time" | ||
|
||
"github.com/aws/aws-sdk-go/private/protocol/eventstream" | ||
) | ||
|
||
const ( | ||
chunkSignatureHeader = ":chunk-signature" | ||
chunkDateHeader = ":date" | ||
) | ||
|
||
// StreamSigner defines an interface for the implementation of signing of event stream payloads | ||
type StreamSigner interface { | ||
GetSignature(headers, payload []byte, date time.Time) ([]byte, error) | ||
} | ||
|
||
// MessageSigner encapsulates signing and attaching signatures to event stream messages | ||
type MessageSigner struct { | ||
Signer StreamSigner | ||
} | ||
|
||
// SignMessage takes the given event stream message generates and adds signature information | ||
// to the event stream message. | ||
func (s MessageSigner) SignMessage(msg *eventstream.Message, date time.Time) error { | ||
msg.Headers.Set(chunkDateHeader, eventstream.TimestampValue(date)) | ||
|
||
var headers bytes.Buffer | ||
if err := eventstream.EncodeHeaders(&headers, msg.Headers); err != nil { | ||
return err | ||
} | ||
|
||
sig, err := s.Signer.GetSignature(headers.Bytes(), msg.Payload, date) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
msg.Headers.Set(chunkSignatureHeader, eventstream.BytesValue(sig)) | ||
|
||
return nil | ||
} |
Oops, something went wrong.