Skip to content
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

fix(middleware-ssec): add logic to handle string input as specified b… #5676

Merged
merged 7 commits into from
Jan 22, 2024
51 changes: 47 additions & 4 deletions packages/middleware-ssec/src/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { ChecksumConstructor } from "@smithy/types";
import * as crypto from "crypto";

import { ssecMiddleware } from "./";

describe("ssecMiddleware", () => {
const next = jest.fn();
const decoder = jest.fn().mockResolvedValue(new Uint8Array(0));
const encoder = jest.fn().mockReturnValue("base64");
const encoder1 = jest.fn();
const encoder2 = jest.fn();
const mockHashUpdate = jest.fn();
const mockHashReset = jest.fn();
const mockHashDigest = jest.fn().mockReturnValue(new Uint8Array(0));
Expand All @@ -17,13 +19,53 @@ describe("ssecMiddleware", () => {
beforeEach(() => {
next.mockClear();
decoder.mockClear();
encoder.mockClear();
encoder1.mockClear();
encoder2.mockClear();
mockHashUpdate.mockClear();
mockHashDigest.mockClear();
mockHashReset.mockClear();
});

it("should base64 encode input keys and set respective MD5 inputs", async () => {
encoder1.mockReturnValue("/+JF8FMG8UVMWSaNz0s6Wg==");
const key = "TestKey123";
const binaryRepresentationOfKey = Buffer.from(key);
const base64Key = binaryRepresentationOfKey.toString("base64");
const md5Hash = crypto.createHash("md5").update(binaryRepresentationOfKey).digest();
const base64Md5Hash = Buffer.from(md5Hash).toString("base64");

const args = {
input: {
SSECustomerKey: base64Key,
CopySourceSSECustomerKey: base64Key,
},
};

const handler = ssecMiddleware({
base64Encoder: encoder1,
utf8Decoder: decoder,
md5: MockHash,
})(next, {} as any);

await handler(args);

expect(next.mock.calls.length).toBe(1);
expect(next).toHaveBeenCalledWith({
input: {
SSECustomerKey: base64Key,
SSECustomerKeyMD5: base64Md5Hash,
CopySourceSSECustomerKey: base64Key,
CopySourceSSECustomerKeyMD5: base64Md5Hash,
},
});
expect(decoder.mock.calls.length).toBe(0);
expect(encoder1.mock.calls.length).toBe(2);
expect(mockHashUpdate.mock.calls.length).toBe(2);
expect(mockHashDigest.mock.calls.length).toBe(2);
encoder1.mockClear();
});
it("should base64 encode input keys and set respective MD5 inputs", async () => {
encoder2.mockReturnValue("base64");
const args = {
input: {
SSECustomerKey: "foo",
Expand All @@ -32,7 +74,7 @@ describe("ssecMiddleware", () => {
};

const handler = ssecMiddleware({
base64Encoder: encoder,
base64Encoder: encoder2,
utf8Decoder: decoder,
md5: MockHash,
})(next, {} as any);
Expand All @@ -49,8 +91,9 @@ describe("ssecMiddleware", () => {
},
});
expect(decoder.mock.calls.length).toBe(2);
expect(encoder.mock.calls.length).toBe(4);
expect(encoder2.mock.calls.length).toBe(4);
expect(mockHashUpdate.mock.calls.length).toBe(2);
expect(mockHashDigest.mock.calls.length).toBe(2);
encoder2.mockClear();
});
});
47 changes: 34 additions & 13 deletions packages/middleware-ssec/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface PreviouslyResolved {
export function ssecMiddleware(options: PreviouslyResolved): InitializeMiddleware<any, any> {
return <Output extends MetadataBearer>(next: InitializeHandler<any, Output>): InitializeHandler<any, Output> =>
async (args: InitializeHandlerArguments<any>): Promise<InitializeHandlerOutput<Output>> => {
let input = { ...args.input };
const input = { ...args.input };
const properties = [
{
target: "SSECustomerKey",
Expand All @@ -36,19 +36,25 @@ export function ssecMiddleware(options: PreviouslyResolved): InitializeMiddlewar
for (const prop of properties) {
const value: SourceData | undefined = (input as any)[prop.target];
if (value) {
const valueView: Uint8Array = ArrayBuffer.isView(value)
? new Uint8Array(value.buffer, value.byteOffset, value.byteLength)
: typeof value === "string"
? options.utf8Decoder(value)
: new Uint8Array(value);
const encoded = options.base64Encoder(valueView);
let valueForHash: Uint8Array;
if (typeof value === "string") {
const isBase64Encoded = /^(?:[A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value);
if (isBase64Encoded) {
Copy link

@tmccombs tmccombs Jan 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the value is a valid base64 string, but isn't actually a base64 encoded value?

I think probably the best way to check this would be to look at the length of the string. If the length of the string is 44 characters, then it is probably a base64 encoded value (for 32 bytes of data). Assuming that AES256 is the encryption algorithm.

valueForHash = base64ToUint8Array(value);
} else {
valueForHash = options.utf8Decoder(value);
input[prop.target] = options.base64Encoder(valueForHash);
}
} else {
valueForHash = ArrayBuffer.isView(value)
? new Uint8Array(value.buffer, value.byteOffset, value.byteLength)
: new Uint8Array(value);
input[prop.target] = options.base64Encoder(valueForHash);
}

const hash = new options.md5();
hash.update(valueView);
input = {
...(input as any),
[prop.target]: encoded,
[prop.hash]: options.base64Encoder(await hash.digest()),
};
hash.update(valueForHash);
input[prop.hash] = options.base64Encoder(await hash.digest());
}
}

Expand All @@ -59,6 +65,21 @@ export function ssecMiddleware(options: PreviouslyResolved): InitializeMiddlewar
};
}

function base64ToUint8Array(base64String: string) {
RanVaknin marked this conversation as resolved.
Show resolved Hide resolved
if (typeof Buffer !== "undefined") {
const buffer = Buffer.from(base64String, "base64");
return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
} else {
const binaryString = atob(base64String);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
}
}

export const ssecMiddlewareOptions: InitializeHandlerOptions = {
name: "ssecMiddleware",
step: "initialize",
Expand Down
Loading