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

padding fixes #321

Merged
merged 10 commits into from
Aug 14, 2024
Merged

padding fixes #321

merged 10 commits into from
Aug 14, 2024

Conversation

andrewkmin
Copy link
Collaborator

@andrewkmin andrewkmin commented Aug 6, 2024

Summary & Motivation

$title

There are cases where we want to manually pad buffers to get to a certain length, primarily when we're dealing with jwks.

The nature of this change is similar in spirit to another change we recently rolled out for our iframes: tkhq/frames#44

How I Tested These Changes

Unit, smoke via #322

Did you add a changeset?

If updating one of our packages, you'll likely need to add a changeset to your PR. To do so, run pnpm changeset. pnpm changeset will generate a file where you should write a human friendly message about the changes. Note how this (example) includes the package name (should be auto added by the command) along with the type of semver change (major.minor.patch) (which you should set).

These changes will be used at release time to determine what packages to publish and how to bump their version. For more context see this comment.

Copy link

codesandbox-ci bot commented Aug 6, 2024

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

@@ -17,7 +17,7 @@ export function base64urlToBuffer(
for (let i = 0; i < str.length; i++) {
byteView[i] = str.charCodeAt(i);
}
return buffer;
return byteView;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is unrelated to this specific change, but up until now this function was essentially just returning an empty buffer.

@andrewkmin andrewkmin changed the title Andrew/padding fixes padding fixes Aug 6, 2024
Copy link
Contributor

@moe-dev moe-dev left a comment

Choose a reason for hiding this comment

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

LGTM assuming tests pass!

uint8ArrayToHexString,
} from "@turnkey/encoding";

const DEFAULT_JWK_MEMBER_BYTE_LENGTH = 32;
Copy link
Contributor

Choose a reason for hiding this comment

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

super tiny nit, if we plan on setting this in multiple files maybe we can export it from @turnkey/encoding

});

test("correctly converts turnkey API key to JWK", function () {
for (let i = 0; i < 100; i++) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Jest doesn't like indefinite (while: true) loops, so just picked an arbitrary fairly large value here

uint8ArrayToHexString(new Uint8Array(decodedX)),
DEFAULT_JWK_MEMBER_BYTE_LENGTH
);
// // Manipulate x and y
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

temporarily commenting this logic out in order to be able to reproduce the issue when converting turnkey API key to jwk

@andrewkmin andrewkmin force-pushed the andrew/padding-fixes branch from 2e4fa27 to 0e269bc Compare August 7, 2024 20:50
Comment on lines 71 to 74
// If a length is specified, ensure we sufficiently pad
let paddedBuffer = new Uint8Array(length);
paddedBuffer.set(buffer, length - buffer.length);
return paddedBuffer;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's important to handle cases where length is less than buffer.length. In this case, maybe an error?

Comment on lines 40 to 42
jwkCopy.d = paddedD;
jwkCopy.x = paddedX;
jwkCopy.y = paddedY;
Copy link
Contributor

@r-n-o r-n-o Aug 9, 2024

Choose a reason for hiding this comment

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

What led to padding (x, y, d) as opposed to just d? Given the jwk is already created I think we're good with the existing (x, y) components within it?

@@ -9,12 +15,39 @@ export function convertTurnkeyApiKeyToJwk(input: {

const jwk = pointDecode(hexStringToUint8Array(compressedPublicKeyHex));
Copy link
Contributor

Choose a reason for hiding this comment

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

If x and y aren't padded for whatever reason, let's fix this inside of pointDecode so we don't have to extract, pad, and then put things "back" in the JWK:

x: Bytes.toBase64(integerToByteArray(x), /* websafe */ true),
y: Bytes.toBase64(integerToByteArray(y), /* websafe */ true),

@andrewkmin andrewkmin force-pushed the andrew/padding-fixes branch 2 times, most recently from 376ac71 to 2425f82 Compare August 10, 2024 02:00
@andrewkmin andrewkmin force-pushed the andrew/padding-fixes branch from 2425f82 to c6d7a1e Compare August 14, 2024 00:18
@andrewkmin andrewkmin requested a review from r-n-o August 14, 2024 01:05
@andrewkmin andrewkmin force-pushed the andrew/padding-fixes branch from 96042d0 to 2d7e5a9 Compare August 14, 2024 13:36
Comment on lines 10 to 13
import {
DEFAULT_JWK_MEMBER_BYTE_LENGTH,
uint8ArrayFromHexString,
} from "@turnkey/encoding";
Copy link
Contributor

Choose a reason for hiding this comment

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

Mmm, this seems a bit off because we have a vendor'd library import our encoding lib :/

@@ -34,12 +38,20 @@ function byteArrayToInteger(bytes: Uint8Array): bigint {
return BigInt("0x" + Bytes.toHex(bytes));
}

/** Converts bigint to byte array. */
function integerToByteArray(i: bigint): Uint8Array {
/** Converts bigint to byte array. This implementation has been modified to optionally augment the resulting byte array to a certain length. */
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's insert this comment at the top of the file so it's obvious (in case someone is trying to update this file down the road)

Comment on lines 46 to 54
if (!length) {
return Bytes.fromHex(input);
}
if (input.length / 2 > length) {
throw new Error(
"hex value cannot fit in a buffer of " + length + " byte(s)"
);
}
return uint8ArrayFromHexString(input, length);
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is necessary. Let's require the length argument (integerToByteArray isn't called anywhere with an undefined length), and pad with 0 without relying on our library function. We can also remove the special case of an odd-length input and just rely on generic padding:

function integerToByteArray(i: bigint, length: number): Uint8Array {
  let input = i.toString(16);
  let numHexChars = length * 2
  if numHexChars < input.length {
    throw new Error(`cannot pack integer with ${input.length} hex chars into$ {length} bytes`);
  } else {
    let padding = "0".repeat(numHexChars - input.length);
  }
  return Bytes.fromHex(padding + input);
}

export function stringToBase64urlString(input: string): string {
// string to base64 -- we do not rely on the browser's btoa since it's not present in React Native environments
const base64String = btoa(input);
return base64StringToBase64UrlEncodedString(base64String);
}

export function base64urlToBuffer(baseurl64String: string): ArrayBuffer {
Copy link
Contributor

Choose a reason for hiding this comment

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

Aside from tests this isn't used anywhere

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

(same as #321 (comment))

baseurl64String.replace(/-/g, "+").replace(/_/g, "/") + padding;

// Base64 to binary string
const str = atob(base64String);
Copy link
Contributor

Choose a reason for hiding this comment

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

Generally we should avoid relying on things that are "magically" available. This may work in Node but might not work in React Native, for example (I'd like our core "encoding" functions to be pure JS so we don't have to deal with polyfills or worry about different implementations across JS runtimes)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That makes sense! This also was cut/pasted from packages/http/src/webauthn-json/base64url.ts / packages/webauthn-stamper/src/webauthn-json/base64url.ts . But if this is "over-consolidation", happy to take this out. That and the fact that this isn't pure JS

Copy link
Contributor

Choose a reason for hiding this comment

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

I see. I lean towards deleting these two functions because they come from a vendored lib https://github.com/tkhq/sdk/blob/main/packages/http/src/webauthn-json/README.md

This will make this diff smaller too! :)

return byteView;
}

export function bufferToBase64url(buffer: ArrayBuffer): string {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here. This doesn't seem to be used anywhere outside of the new tests.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was planning on consolidating various utils in packages/encoding instead of having the same util functions in http (specifically packages/http/src/webauthn-json/base64url.ts)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ended up removing for now! maybe we can move it back, consolidate utilities in encoding down the line, along with a pureJS implementation of atob (similar to the btoa impl we have vendored in)

@@ -1,5 +1,6 @@
/**
* Code modified from https://github.com/google/tink/blob/6f74b99a2bfe6677e3670799116a57268fd067fa/javascript/subtle/elliptic_curves.ts
* - The implementation of integerToByteArray has been modified to optionally augment the resulting byte array to a certain length.
Copy link
Contributor

Choose a reason for hiding this comment

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

not optionally anymore :)

baseurl64String.replace(/-/g, "+").replace(/_/g, "/") + padding;

// Base64 to binary string
const str = atob(base64String);
Copy link
Contributor

Choose a reason for hiding this comment

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

I see. I lean towards deleting these two functions because they come from a vendored lib https://github.com/tkhq/sdk/blob/main/packages/http/src/webauthn-json/README.md

This will make this diff smaller too! :)

Copy link
Contributor

@r-n-o r-n-o left a comment

Choose a reason for hiding this comment

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

Looks good!

@andrewkmin andrewkmin merged commit 1b0306d into main Aug 14, 2024
5 checks passed
@andrewkmin andrewkmin deleted the andrew/padding-fixes branch August 14, 2024 20:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants