sigv4.test.js (4101B)
1 // test/sigv4.test.js - AWS Signature Version 4 2 // 3 // The vectors come from the published AWS signature test suite. Getting this 4 // wrong produces an opaque SignatureDoesNotMatch from the server, so the 5 // reference cases are worth pinning. 6 7 import test from 'node:test'; 8 import assert from 'node:assert/strict'; 9 import { signRequest, presignUrl, uriEncode, amzDate, EMPTY_SHA256 } from '../src/lib/storage/sigv4.js'; 10 11 const CREDS = { 12 region: 'us-east-1', 13 service: 'service', 14 accessKeyId: 'AKIDEXAMPLE', 15 secretAccessKey: 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY', 16 date: new Date(Date.UTC(2015, 7, 30, 12, 36, 0)), 17 }; 18 19 test('get-vanilla matches the reference signature', () => { 20 const headers = signRequest({ 21 method: 'GET', 22 url: new URL('https://example.amazonaws.com/'), 23 payloadHash: EMPTY_SHA256, 24 ...CREDS, 25 }); 26 27 assert.equal( 28 headers.authorization, 29 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, ' + 30 'SignedHeaders=host;x-amz-date, ' + 31 'Signature=5fa00fa31553b73ebf1942676e86291e8372ff2a2260956d9b8aae1d763fbf31' 32 ); 33 }); 34 35 test('get-vanilla-query-order-key-case matches the reference signature', () => { 36 const headers = signRequest({ 37 method: 'GET', 38 url: new URL('https://example.amazonaws.com/?Param2=value2&Param1=value1'), 39 payloadHash: EMPTY_SHA256, 40 ...CREDS, 41 }); 42 43 assert.equal( 44 headers.authorization, 45 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, ' + 46 'SignedHeaders=host;x-amz-date, ' + 47 'Signature=b97d918cfa904a5beff61c982a1b6f458b799221646efd99d3219ec94cdf2500' 48 ); 49 }); 50 51 test('uriEncode percent encodes the characters encodeURIComponent leaves alone', () => { 52 assert.equal(uriEncode("a b!'()*"), 'a%20b%21%27%28%29%2A'); 53 assert.equal(uriEncode('keep~unreserved-._'), 'keep~unreserved-._'); 54 assert.equal(uriEncode('a/b'), 'a%2Fb'); 55 assert.equal(uriEncode('a/b', false), 'a/b'); 56 }); 57 58 test('amzDate produces both required formats', () => { 59 const { amz, stamp } = amzDate(new Date(Date.UTC(2015, 7, 30, 12, 36, 0))); 60 assert.equal(amz, '20150830T123600Z'); 61 assert.equal(stamp, '20150830'); 62 }); 63 64 test('s3 requests carry the content hash header', () => { 65 const headers = signRequest({ 66 method: 'PUT', 67 url: new URL('https://bucket.example.com/key'), 68 payloadHash: EMPTY_SHA256, 69 region: 'us-east-1', 70 service: 's3', 71 accessKeyId: 'AKIDEXAMPLE', 72 secretAccessKey: 'secret', 73 }); 74 assert.equal(headers['x-amz-content-sha256'], EMPTY_SHA256); 75 assert.ok(headers.authorization.includes('x-amz-content-sha256')); 76 }); 77 78 test('presigned urls carry the signature in the query string', () => { 79 const url = new URL(presignUrl({ 80 method: 'GET', 81 url: new URL('https://example.com/bucket/key'), 82 expires: 900, 83 region: 'us-east-1', 84 service: 's3', 85 accessKeyId: 'AKIDEXAMPLE', 86 secretAccessKey: 'secret', 87 })); 88 89 assert.equal(url.searchParams.get('X-Amz-Algorithm'), 'AWS4-HMAC-SHA256'); 90 assert.equal(url.searchParams.get('X-Amz-Expires'), '900'); 91 assert.equal(url.searchParams.get('X-Amz-SignedHeaders'), 'host'); 92 assert.match(url.searchParams.get('X-Amz-Signature'), /^[0-9a-f]{64}$/); 93 }); 94 95 test('an already encoded path is not encoded a second time', () => { 96 // A key containing a space reaches the signer as %20. Re-encoding it to 97 // %2520 is the bug this guards against. 98 const url = new URL('https://example.com/bucket/a%20b'); 99 const headers = signRequest({ 100 method: 'GET', 101 url, 102 payloadHash: EMPTY_SHA256, 103 region: 'us-east-1', 104 service: 's3', 105 accessKeyId: 'AKIDEXAMPLE', 106 secretAccessKey: 'secret', 107 }); 108 109 // Signing the same path written differently must differ, proving the path 110 // is taken verbatim rather than normalized. 111 const other = signRequest({ 112 method: 'GET', 113 url: new URL('https://example.com/bucket/a%2520b'), 114 payloadHash: EMPTY_SHA256, 115 region: 'us-east-1', 116 service: 's3', 117 accessKeyId: 'AKIDEXAMPLE', 118 secretAccessKey: 'secret', 119 }); 120 121 assert.notEqual(headers.authorization, other.authorization); 122 });