Skip to content
This repository was archived by the owner on Mar 8, 2024. It is now read-only.

Commit 1d8d293

Browse files
authored
Jose 3 upgrade - browser compatibility (#59)
* update version for release * initial conversion to JOSE 3.4.0 * clean up linting errors * revert package.json changes that weren't necessary * revert package.json changes that weren't necessary * remove unused method * clean up untyped checking * revert back to payIdAddress json property since that has not yet changed in API spec
1 parent 3170515 commit 1d8d293

13 files changed

+297
-281
lines changed

.eslintrc.js

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,17 @@ module.exports = {
2525
'@xpring-eng/eslint-config-mocha',
2626
'@xpring-eng/eslint-config-base',
2727
],
28-
29-
rules: {},
30-
overrides: [
31-
{
32-
"files": ["*cli.ts"],
33-
"rules": {
34-
"node/shebang": "off"
35-
},
36-
},
37-
{
38-
"files": ["src/commands/*.ts"],
39-
"rules": {
40-
"class-methods-use-this": "off"
41-
},
28+
rules: {
29+
// linter doesn't seem to understand ESM imports used by jose, even though typescript handles them just fine.
30+
"node/no-missing-import": ["error", {
31+
allowModules: ["jose"],
4232
}],
33+
"import/no-unresolved": [
34+
2,
35+
{
36+
ignore: [
37+
'jose'
38+
]
39+
}],
40+
},
4341
}

package-lock.json

Lines changed: 3 additions & 42 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@
2525
"test": "nyc mocha 'test/**/*.test.ts'"
2626
},
2727
"dependencies": {
28-
"axios": "^0.19.2",
29-
"jose": "^1.27.3"
28+
"jose": "^3.4.0"
3029
},
3130
"devDependencies": {
3231
"@arkweid/lefthook": "^0.7.2",

src/verifiable/converters.ts

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { JWS } from 'jose'
1+
import { GeneralJWSInput, GeneralJWS } from 'jose/webcrypto/types'
22

33
import {
44
Address,
@@ -7,27 +7,44 @@ import {
77
} from './verifiable-paystring'
88

99
/**
10-
* Converts a GeneralJWS to a Verified Address. Both have the same JWS structure but for
10+
* Converts JSON to a Verified Address. Both have the same JWS structure but for
1111
* PayString we use our own type so as not to be coupled too tightly to the underlying JOSE library.
1212
*
13-
* @param jws - The JWS to convert.
13+
* @param json - The json to convert.
1414
* @returns The address.
15-
* @throws Error if the JWS does not have the required properties.
15+
* @throws Error if the json does not have the required properties.
1616
*/
17-
export function convertToVerifiedAddress(jws: JWS.GeneralJWS): VerifiedAddress {
18-
const verified: VerifiedAddress = {
19-
payload: jws.payload,
20-
signatures: jws.signatures.map((value) => {
21-
if (value.protected) {
22-
return {
23-
protected: value.protected,
24-
signature: value.signature,
25-
}
17+
export function convertToVerifiedAddress(json: string): VerifiedAddress {
18+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- untyped JSON
19+
const { address }: { address: VerifiedAddress } = JSON.parse(json)
20+
return address
21+
}
22+
23+
/**
24+
* Converts JWS to a Verified Address. Both have the same JWS structure but for
25+
* PayString we use our own type so as not to be coupled too tightly to the underlying JOSE library.
26+
*
27+
* @param payload - The payload.
28+
* @param jws - The JWS signatures.
29+
* @returns The address.
30+
* @throws Error if the json does not have the required properties.
31+
*/
32+
export function convertGeneralJwsToVerifiedAddress(
33+
payload: string,
34+
jws: GeneralJWS,
35+
): VerifiedAddress {
36+
return {
37+
payload,
38+
signatures: jws.signatures.map((recipient) => {
39+
if (recipient.protected === undefined) {
40+
throw new Error('missing protected property')
41+
}
42+
return {
43+
protected: recipient.protected,
44+
signature: recipient.signature,
2645
}
27-
throw Error('missing protected property')
2846
}),
2947
}
30-
return verified
3148
}
3249

3350
/**
@@ -38,8 +55,28 @@ export function convertToVerifiedAddress(jws: JWS.GeneralJWS): VerifiedAddress {
3855
*/
3956
export function convertJsonToAddress(json: string): Address {
4057
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- untyped JSON
41-
const { payStringAddress }: { payStringAddress: Address } = JSON.parse(json)
42-
return payStringAddress
58+
const { payIdAddress }: { payIdAddress: Address } = JSON.parse(json)
59+
return payIdAddress
60+
}
61+
62+
/**
63+
* Converts a VerifiedAddress to a JOSE GeneralJWSInput.
64+
*
65+
* @param verifiedAddress - Address to convert.
66+
* @returns Address instance.
67+
*/
68+
export async function convertToGeneralJWSInput(
69+
verifiedAddress: VerifiedAddress,
70+
): Promise<GeneralJWSInput> {
71+
return {
72+
payload: verifiedAddress.payload,
73+
signatures: verifiedAddress.signatures.map((value) => {
74+
return {
75+
signature: value.signature,
76+
protected: value.protected,
77+
}
78+
}),
79+
}
4380
}
4481

4582
/**

src/verifiable/identity-key-signing-params.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
1-
import { JWK } from 'jose'
1+
import { JWK } from 'jose/webcrypto/types'
22

33
import { SigningParams } from './verifiable-paystring'
44

5-
import ECKey = JWK.ECKey
6-
import RSAKey = JWK.RSAKey
7-
import OctKey = JWK.OctKey
8-
import OKPKey = JWK.OKPKey
9-
105
/**
116
* Represents the properties needed to sign a PayString using an identity key.
127
*/
138
export default class IdentityKeySigningParams implements SigningParams {
149
public readonly keyType = 'identityKey'
15-
public readonly key: ECKey | RSAKey | OctKey | OKPKey
10+
public readonly key: JWK
1611
public readonly alg: string
1712

1813
/**
@@ -21,7 +16,7 @@ export default class IdentityKeySigningParams implements SigningParams {
2116
* @param key - The private key to sign with.
2217
* @param alg - The signing algorithm.
2318
*/
24-
public constructor(key: ECKey | RSAKey | OctKey | OKPKey, alg: string) {
19+
public constructor(key: JWK, alg: string) {
2520
this.key = key
2621
this.alg = alg
2722
}

src/verifiable/keys.ts

Lines changed: 41 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,44 @@
1-
import { promises } from 'fs'
1+
import fromKeyLike from 'jose/jwk/from_key_like'
2+
import calculateThumbprint from 'jose/jwk/thumbprint'
3+
import generateKeyPair from 'jose/util/generate_key_pair'
4+
import { JWK } from 'jose/webcrypto/types'
25

3-
import { JWK, JWKECKey, JWKOctKey, JWKOKPKey, JWKRSAKey, JWS } from 'jose'
4-
5-
import RSAKey = JWK.RSAKey
6-
import ECKey = JWK.ECKey
7-
import OKPKey = JWK.OKPKey
8-
import OctKey = JWK.OctKey
9-
import JWSRecipient = JWS.JWSRecipient
6+
import {
7+
ProtectedHeaders,
8+
VerifiedAddressSignature,
9+
} from './verifiable-paystring'
1010

11+
const DEFAULT_ALGORITHM = 'ES256'
1112
const DEFAULT_CURVE = 'P-256'
1213

13-
/**
14-
* Reads JWK key from a file.
15-
*
16-
* @param path - The full file path of the key file.
17-
* @returns A JWK key.
18-
*/
19-
export async function getSigningKeyFromFile(
20-
path: string,
21-
): Promise<RSAKey | ECKey | OKPKey | OctKey> {
22-
const pem = await promises.readFile(path, 'ascii')
23-
return JWK.asKey(pem)
24-
}
25-
26-
/**
27-
* Reads JWK from a file. If file contains a chain of certificates, JWK is generated from the first
28-
* certificate and the rest of the certificates in file are added to the x5c section of the JWK.
29-
*
30-
* @param path - The full file path of the key file.
31-
* @returns A JWK key.
32-
*/
33-
export async function getJwkFromFile(
34-
path: string,
35-
): Promise<JWKRSAKey | JWKECKey | JWKOKPKey | JWKOctKey> {
36-
const content = await promises.readFile(path, 'ascii')
37-
return JWK.asKey(content).toJWK(false)
38-
}
39-
4014
/**
4115
* Extracts the JWK property from the base64 json in the protected section of a JWK recipient.
4216
*
4317
* @param recipient - The recipient to process.
4418
* @returns The JWK if found, otherwise undefined.
4519
*/
4620
export function getJwkFromRecipient(
47-
recipient: JWSRecipient,
48-
): JWKRSAKey | JWKECKey | JWKOKPKey | JWKOctKey | undefined {
21+
recipient: VerifiedAddressSignature,
22+
): JWK | undefined {
4923
if (recipient.protected) {
5024
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- because JSON
51-
const headers = JSON.parse(
25+
const headers: ProtectedHeaders = JSON.parse(
5226
Buffer.from(recipient.protected, 'base64').toString('utf-8'),
5327
)
54-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- because JSON
55-
if (headers.jwk) {
56-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- because JSON
57-
return JWK.asKey(headers.jwk).toJWK(false)
58-
}
28+
return headers.jwk
5929
}
6030
return undefined
6131
}
6232

63-
/**
64-
* The jose library has 2 types of instances: JWK<Type>Key and JWK.<TypeKey>. The former is just data interface with no
65-
* methods where as the latter is a richer type. This method converts from the data interface to the richer type.
66-
*
67-
* @param jwk - The instance to convert.
68-
* @returns The converted result.
69-
*/
70-
export function toKey(
71-
jwk: JWKRSAKey | JWKECKey | JWKOctKey | JWKOKPKey,
72-
): JWK.RSAKey | JWK.ECKey | JWK.OKPKey | JWK.OctKey {
73-
// JWKRSAKey, JWKECKey, etc use typescript conditional typing so
74-
// these if conditions are needed to target the correct method overload.
75-
if (jwk.kty === 'EC') {
76-
return JWK.asKey(jwk)
77-
}
78-
if (jwk.kty === 'oct') {
79-
return JWK.asKey(jwk)
80-
}
81-
if (jwk.kty === 'OKP') {
82-
return JWK.asKey(jwk)
83-
}
84-
return JWK.asKey(jwk)
85-
}
86-
8733
/**
8834
* Gets the default algorithm for a given JWK*Key instance.
8935
*
9036
* @param jwk - The jwk.
9137
* @returns The default algorithm to use.
9238
*/
93-
export function getDefaultAlgorithm(
94-
jwk: JWKRSAKey | JWKECKey | JWKOctKey | JWKOKPKey,
95-
): string {
39+
export function getDefaultAlgorithm(jwk: JWK): string {
9640
if (jwk.kty === 'EC') {
97-
return 'ES256'
41+
return DEFAULT_ALGORITHM
9842
}
9943
if (jwk.kty === 'oct') {
10044
return 'HS512'
@@ -110,6 +54,30 @@ export function getDefaultAlgorithm(
11054
*
11155
* @returns A JWK key.
11256
*/
113-
export async function generateNewKey(): Promise<ECKey> {
114-
return JWK.generate('EC', DEFAULT_CURVE)
57+
export async function generateNewKey(): Promise<JWK> {
58+
const { privateKey } = await generateKeyPair(DEFAULT_ALGORITHM, {
59+
crv: DEFAULT_CURVE,
60+
})
61+
return fromKeyLike(privateKey).then(async (key: JWK) => {
62+
return calculateThumbprint(key).then((thumbprint: string) => {
63+
return {
64+
kid: thumbprint,
65+
alg: getDefaultAlgorithm(key),
66+
use: 'sig',
67+
...key,
68+
}
69+
})
70+
})
71+
}
72+
73+
/**
74+
* Convert a JWK with a private key, to a JWK without a private key.
75+
*
76+
* @param jwk - The jwk.
77+
* @returns A JWK key.
78+
*/
79+
export function toPublicJWK(jwk: JWK): JWK {
80+
// eslint-disable-next-line id-length --- 'd' is an actual property of a JWK
81+
const { d, ...rest } = jwk
82+
return { ...rest }
11583
}

0 commit comments

Comments
 (0)