package v4a import ( "context" "crypto/sha256" "encoding/hex" "fmt" "strings" "time" signerCrypto "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4asdk2/internal/crypto" v4Internal "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4asdk2/internal/v4" ) // EventStreamSigner is an AWS EventStream protocol signer. type EventStreamSigner interface { GetSignature(ctx context.Context, headers, payload []byte, signingTime time.Time, optFns ...func(*StreamSignerOptions)) ([]byte, error) } // StreamSignerOptions is the configuration options for StreamSigner. type StreamSignerOptions struct{} // StreamSigner implements Signature Version 4 (SigV4) signing of event stream encoded payloads. type StreamSigner struct { options StreamSignerOptions credentials Credentials service string prevSignature []byte } // NewStreamSigner returns a new AWS EventStream protocol signer. func NewStreamSigner(credentials Credentials, service string, seedSignature []byte, optFns ...func(*StreamSignerOptions)) *StreamSigner { o := StreamSignerOptions{} for _, fn := range optFns { fn(&o) } return &StreamSigner{ options: o, credentials: credentials, service: service, prevSignature: seedSignature, } } func (s *StreamSigner) VerifySignature(headers, payload []byte, signingTime time.Time, signature []byte, optFns ...func(*StreamSignerOptions)) error { options := s.options for _, fn := range optFns { fn(&options) } prevSignature := s.prevSignature st := v4Internal.NewSigningTime(signingTime) scope := buildCredentialScope(st, s.service) stringToSign := s.buildEventStreamStringToSign(headers, payload, prevSignature, scope, &st) ok, err := signerCrypto.VerifySignature(&s.credentials.PrivateKey.PublicKey, makeHash(sha256.New(), []byte(stringToSign)), signature) if err != nil { return err } if !ok { return fmt.Errorf("v4a: invalid signature") } s.prevSignature = signature return nil } func (s *StreamSigner) buildEventStreamStringToSign(headers, payload, previousSignature []byte, credentialScope string, signingTime *v4Internal.SigningTime) string { hash := sha256.New() return strings.Join([]string{ "AWS4-ECDSA-P256-SHA256-PAYLOAD", signingTime.TimeFormat(), credentialScope, hex.EncodeToString(previousSignature), hex.EncodeToString(makeHash(hash, headers)), hex.EncodeToString(makeHash(hash, payload)), }, "\n") } func buildCredentialScope(st v4Internal.SigningTime, service string) string { return strings.Join([]string{ st.Format(shortTimeFormat), service, "aws4_request", }, "/") }