-
Notifications
You must be signed in to change notification settings - Fork 34
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
Conversation
|
✅ Deploy Preview for openpayments-preview canceled.
|
There was a problem hiding this 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.
const PCKS8_SEQUENCE_PARTS = new Uint8Array([ | ||
48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32 | ||
]) |
There was a problem hiding this comment.
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.
x: binaryToBase64url(publicKeyBinary), | ||
d: binaryToBase64url(binary) |
There was a problem hiding this comment.
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)
const body = | ||
typeof Buffer !== 'undefined' | ||
? Buffer.from(privateKey).toString('base64') | ||
: btoa(String.fromCharCode(...privateKey)) |
There was a problem hiding this comment.
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.
const OID = '1.3.101.112' | ||
const CURVE = 'curveEd25519' |
There was a problem hiding this comment.
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.
// 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 |
There was a problem hiding this comment.
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 theversion 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 theprivateKeyAlgorithm 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 thealgorithm 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?
There was a problem hiding this comment.
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.
let base64: string | undefined | ||
if (typeof Buffer !== 'undefined') { | ||
base64 = Buffer.from(value, 'binary').toString('base64') | ||
} else { | ||
base64 = btoa(value) | ||
} |
There was a problem hiding this comment.
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.
Changes proposed in this pull request
Context
Closes #416.