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

feat(sig-utils): add browser support #417

Closed
wants to merge 11 commits into from
Closed

Conversation

raducristianpopa
Copy link
Member

Changes proposed in this pull request

Context

Closes #416.

Copy link

changeset-bot bot commented Feb 6, 2024

⚠️ No Changeset found

Latest commit: 14fb4df

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

netlify bot commented Feb 6, 2024

Deploy Preview for openpayments-preview canceled.

Name Link
🔨 Latest commit 14fb4df
🔍 Latest deploy log https://app.netlify.com/sites/openpayments-preview/deploys/65c37a89b0ed520007cb4bac

Copy link
Member Author

@raducristianpopa raducristianpopa left a comment

Choose a reason for hiding this comment

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

This draft pull request initiates the addition of browser support to the library. Future pull requests will target this branch to incrementally update all parts.

Given the current challenge - no browser support or experimental feature for our algorithm:

  • would it fit best to release the libraries with browser compatibility under a specific tag? This would enable the WM extension to take advantage of the Open Payments SDK without replicating its capabilities and to prevent vulnerabilities from being introduced into the stable releases. Another option is to create a new directory named "browser," from which we can export the required methods to enable functionality in web browsers. This way we are not touching the stable parts of the libraries.

  • keep libraries as is (only update the http-message-signatures version and fix its breaking changes) and create a custom client for the SDK (a la feat(op): add monetization client #413). This way, we can move the signature generation on the library user side - in our use case, we will write our own signature generation logic in the extension.

Comment on lines +15 to +17
const PCKS8_SEQUENCE_PARTS = new Uint8Array([
48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32
])
Copy link
Member Author

Choose a reason for hiding this comment

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

This represents the first two PKCS#8 sequence elements (version, algorithm). Generating the private key with @noble/ed25519 only returns the the raw value of the private key.

Comment on lines +61 to +62
x: binaryToBase64url(publicKeyBinary),
d: binaryToBase64url(binary)
Copy link
Member Author

Choose a reason for hiding this comment

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

For a JWK, the x and d properties need to be base64url encoded.

  • "x" - the x coordinate (public key)
  • "d" - private exponent (private key)

Comment on lines +33 to +36
const body =
typeof Buffer !== 'undefined'
? Buffer.from(privateKey).toString('base64')
: btoa(String.fromCharCode(...privateKey))
Copy link
Member Author

Choose a reason for hiding this comment

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

For a PEM format, the body needs to be only base64 encoded.

Comment on lines +119 to +120
const OID = '1.3.101.112'
const CURVE = 'curveEd25519'
Copy link
Member Author

Choose a reason for hiding this comment

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

Unique identifiers for the algorithm.

Comment on lines 130 to 152
// Header length: 2
// PKCS8 sequence length: 46
if (pkcs8Sequence.length + pkcs8Sequence.header !== 48) return false
if (!pkcs8Sequence.sub) return false
// Sequence containing 3 elements (version, algorithm, and private key)
if (pkcs8Sequence.sub.length !== 3) return false

const [, algorithmSequence] = pkcs8Sequence.sub

// Header length: 2
// Algorithm length: 5
if (algorithmSequence.length + algorithmSequence.header !== 7) return false
// Sequence containing 1 element (algorithm identifier)
if (!algorithmSequence.sub) throw new Error('Invalid algorithm sequence.')
if (algorithmSequence.sub.length !== 1) return false

const algorithm = algorithmSequence.sub[0].content()

if (!algorithm) return false

const [oid, curve] = algorithm.split('\n')

if (oid !== OID || curve !== CURVE) return false
Copy link
Member Author

Choose a reason for hiding this comment

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

  • header: represents the ASN1 type of the element

Example ASN.1 decoded PKCS#8.

PrivateKeyInfo SEQUENCE (3 elem) # header length: 2
  version Version INTEGER 0 # header: 2; value length: 1
  privateKeyAlgorithm AlgorithmIdentifier SEQUENCE (1 elem) # header length: 2
    algorithm OBJECT IDENTIFIER 1.3.101.112 curveEd25519 (EdDSA 25519 signature algorithm) # header length: 2; value: length 3
  privateKey PrivateKey OCTET STRING (34 byte) 04205641D45CD7F95FEB2635A2E012AE721A2586A33B44C822718BC8EBC173CC7C3D
    OCTET STRING (32 byte) 5641D45CD7F95FEB2635A2E012AE721A2586A33B44C822718BC8EBC173CC7C3D # header length: 2; value length: 32

Hex dump:

30 2E 02 01 00 30 05 06  03 2B 65 70 04 22 04 20
56 41 D4 5C D7 F9 5F EB  26 35 A2 E0 12 AE 72 1A
25 86 A3 3B 44 C8 22 71  8B C8 EB C1 73 CC 7C 3D

To ensure the key was created using the Ed25519 algorithm, we must verify the algorithm used. This involves identifying the sequence element that denotes the private key algorithm.

For the privateKeyAlgorithm:

  • The offset is calculated by adding the length of the PrivateKeyInfo SEQUENCE header to the length of the version Version (header plus value), which equals 5 [2 + (2 + 1)];
  • The end position (length) is found by adding the privateKeyAlgorithm (5) offset to the length of the privateKeyAlgorithm AlgorithmIdentifier SEQUENCE header, resulting in 7 [5 + 2];

For the algorithm itself:

  • The offset is the end position of the privateKeyAlgorithm, 7.
  • The end position (length) is determined by adding the algorithm offset to the length of the algorithm OBJECT IDENTIFIER (header plus value), which equals 12 [7 + (2 + 3)];

Currently, based on the above calculations, the section that denotes the algorithm is clearly visible in the hex dump:

30 2E 02 01 00 30 05 [[06  03 2B 65 70]] 04 22 04 20

(Use the @lapo/asn1.js web application to visualize the ASN1 in a more readable format)

The @lapo/asn1js library is used for decoding keys into ASN1 format. As a general library supporting all ASN1 types, it must also maintain a record of all ASN1 object identifiers (OIDs). Within the library, there's a specific file dedicated to storing all these identifiers - its size being around 210kb.

Searching for alternative ASN1 libraries, it looks like that they all come with a significant unpacked size. The question is: should we include an additional 200-300kb in the http-signature-utils library?

Copy link
Member Author

Choose a reason for hiding this comment

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

If we do not want to use any libraries to validate the private key algorithm, a straightforward approach would involve identifying the specific element denoting the algorithm (06 03 2B 65 70) and converting it into dot notation, which will represent the OID for the Ed25519 curve.

Comment on lines 13 to 18
let base64: string | undefined
if (typeof Buffer !== 'undefined') {
base64 = Buffer.from(value, 'binary').toString('base64')
} else {
base64 = btoa(value)
}
Copy link
Member Author

Choose a reason for hiding this comment

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

atob and btoa are marked as deprecated in Node.

@raducristianpopa
Copy link
Member Author

Closing in favor of #412 and #420.

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.

Transition to SubtleCrypto from Node's crypto module
1 participant