Introduction#

GitHub this note shows how to use AWS Signature V4 to sign a request

Setup Project#

Let create a new typescript project

npm init

Install typescript

npm install typescript --save-dev

Install ambient Node.js types for typescript

npm install @types/node --save-dev

Create a tsconfig.json as following

{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"lib": ["es6"],
"allowJs": true,
"outDir": "build",
"rootDir": "src",
"strict": true,
"noImplicitAny": true,
"esModuleInterop": true,
"resolveJsonModule": true
}
}

Create Signature#

There are different ways or tools to create a signature v4

  • step by step following the docs
  • use @aws-sdk/signature-v4 or @aws-sdk/signature-v4-crt here
  • use python lib here

Install dependencies

npm install @aws-sdk/signature-v4 @aws-sdk/protocol-http @aws-crypto/sha256-js axios

Create a signature signer

const signer = new SignatureV4({
credentials: {
accessKeyId: config.accessKeyId,
secretAccessKey: config.secretAccessKey
},
region: 'us-east-1',
service: 's3',
sha256: Sha256
})

Create a request

const request = new HttpRequest({
path: new URL(config.endpoint).pathname,
hostname: new URL(config.endpoint).hostname,
protocol: 'https',
method: 'GET',
headers: {
host: new URL(config.endpoint).hostname
}
})

Sign the request

signer
.sign(request, {
signingRegion: 'us-east-1',
signingService: 's3',
signingDate: new Date()
})
.then(response => {
console.log(response)
axios
.get(config.endpoint, {
headers: response.headers,
responseType: 'stream'
})
.then(response => {
console.log(response)
response.data.pipe(fs.createWriteStream('./tree.jpg'))
})
.catch(error => {
console.log(error)
})
})

Send Request#

After signed request, we can send the request to an S3 endpoint to get an image object

signer
.sign(request, {
signingRegion: 'us-east-1',
signingService: 's3',
signingDate: new Date()
})
.then(response => {
console.log(response)
axios
.get(config.endpoint, {
headers: response.headers,
responseType: 'stream'
})
.then(response => {
console.log(response)
response.data.pipe(fs.createWriteStream('./tree.jpg'))
})
.catch(error => {
console.log(error)
})
})

Smithy SignatureV4#

Let update using the Smithy SignatureV4

{
"name": "smithy-signature-v4",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@aws-crypto/sha256-js": "^5.2.0",
"@aws-sdk/credential-providers": "^3.515.0",
"@aws-sdk/protocol-http": "^3.374.0",
"@smithy/signature-v4": "^2.1.1",
"axios": "^1.6.7"
},
"devDependencies": {
"@types/node": "^20.11.19",
"typescript": "^5.3.3"
}
}

Here is the detail code

smithy-signature-v4.ts
import { SignatureV4 } from '@smithy/signature-v4'
import { HttpRequest } from '@aws-sdk/protocol-http'
import { config } from './config'
import { Sha256 } from '@aws-crypto/sha256-js'
import { fromIni } from '@aws-sdk/credential-providers'
import axios from 'axios'
import * as fs from 'fs'
const request = new HttpRequest({
path: new URL(config.endpoint).pathname,
hostname: new URL(config.endpoint).hostname,
protocol: 'https',
method: 'GET',
headers: {
host: new URL(config.endpoint).hostname
}
})
const signer = new SignatureV4({
// credentials: {
// accessKeyId: config.accessKeyId,
// secretAccessKey: config.secretAccessKey,
// sessionToken: config.sessionToken,
// },
credentials: fromIni({
profile: 'default'
}),
region: 'us-east-1',
service: 's3',
sha256: Sha256
})
signer
.sign(request, {
signingRegion: 'us-east-1',
signingService: 's3',
signingDate: new Date()
})
.then(response => {
console.log(response)
axios
.get(config.endpoint, {
headers: response.headers,
responseType: 'stream'
})
.then(response => {
console.log(response)
response.data.pipe(fs.createWriteStream('./tree.jpg'))
})
.catch(error => {
console.log(error)
})
})

Golang#

Similarly, let create a SignatureV4 using Go SDK

  • Create a http request
  • Sign the request
  • Send the request to s3

Here is detail script

main.go
// https://github.com/bmizerany/aws4/blob/master/sign.go
// https://github.com/mousedownmike/go-lambda-v4-signature
package main
import (
"context"
"fmt"
"io"
"log"
"net/http"
"os"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
"github.com/aws/aws-sdk-go/aws/session"
)
const imageUrl = "https://s3.ap-southeast-1.amazonaws.com/BUCKET-NAME/whale.jpg"
func main() {
ctx := context.TODO()
request1, _ := http.NewRequest(http.MethodGet, imageUrl, nil)
awsSess, err := session.NewSessionWithOptions(session.Options{
SharedConfigState: session.SharedConfigEnable,
})
if err != nil {
log.Fatalf("failed creating session: %s", err)
}
value, _ := awsSess.Config.Credentials.Get()
credentials := &aws.Credentials{
AccessKeyID: value.AccessKeyID,
SecretAccessKey: value.SecretAccessKey,
SessionToken: value.SessionToken,
}
signer := v4.NewSigner()
error := signer.SignHTTP(
ctx,
*credentials,
request1,
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
*aws.String("s3"),
*aws.String("ap-southeast-1"),
time.Now(),
)
// hot fix as x-amz-content-sha256 not inserted in the header
request1.Header.Set("X-Amz-Content-Sha256", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
fmt.Println(request1.Header)
if error != nil {
log.Println(error)
}
client := &http.Client{}
response, error := client.Do(request1)
if error != nil {
log.Println(error)
}
fmt.Println(response)
bytes, error := io.ReadAll(response.Body)
if (error != nil){
fmt.Println(error)
}
f, _ := os.Create("hehe.jpg")
_,errorx := f.Write(bytes)
if (errorx != nil){
fmt.Println(errorx)
}
_ = f.Close()
}

Reference#