@flickfyi/flkr 0.0.153 → 0.0.167

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/node_modules/@flickfyi/attestation/.turbo/turbo-build.log +5 -5
  2. package/node_modules/@flickfyi/attestation/dist/index.js +2 -2
  3. package/node_modules/@flickfyi/attestation/src/verify-attestation.ts +5 -3
  4. package/node_modules/@flickfyi/attestation/tests/verify-attestation.test.ts +67 -1
  5. package/node_modules/@flickfyi/biscuit/.turbo/turbo-build.log +4 -4
  6. package/node_modules/@flickfyi/core/.turbo/turbo-build.log +85 -85
  7. package/node_modules/@flickfyi/http-message-sig/.turbo/turbo-build.log +6 -6
  8. package/node_modules/@flickfyi/http-message-sig/dist/index.js +683 -3
  9. package/node_modules/@flickfyi/http-message-sig/package.json +3 -2
  10. package/node_modules/@flickfyi/http-message-sig/src/build.ts +3 -1
  11. package/node_modules/@flickfyi/http-message-sig/tests/build.spec.ts +6 -0
  12. package/node_modules/@flickfyi/identity/.turbo/turbo-build.log +5 -5
  13. package/node_modules/@flickfyi/identity/README.md +36 -0
  14. package/node_modules/@flickfyi/identity/dist/index.d.ts +9 -6
  15. package/node_modules/@flickfyi/identity/dist/index.js +1 -1
  16. package/node_modules/@flickfyi/identity/src/types.ts +8 -3
  17. package/node_modules/@flickfyi/identity/src/verify-attestation.ts +10 -6
  18. package/node_modules/@flickfyi/identity/tests/fixtures/expired-attestation.json +9 -0
  19. package/node_modules/@flickfyi/identity/tests/supplied-attestation.test.ts +104 -0
  20. package/node_modules/@flickfyi/lens/.turbo/turbo-build.log +4 -4
  21. package/node_modules/@flickfyi/photon/.turbo/turbo-build.log +9 -3
  22. package/node_modules/@flickfyi/rwsdk-extensions/.turbo/turbo-build.log +4 -4
  23. package/node_modules/@flickfyi/rwsdk-extensions/tests/lifecycles-worker/src/answer-42.wasm +0 -0
  24. package/node_modules/@flickfyi/rwsdk-extensions/tests/lifecycles-worker/src/answer-43.wasm +0 -0
  25. package/node_modules/@flickfyi/rwsdk-extensions/tests/lifecycles-worker/src/wasm.d.ts +4 -0
  26. package/node_modules/@flickfyi/rwsdk-extensions/tests/lifecycles-worker/src/worker.ts +14 -0
  27. package/node_modules/@flickfyi/rwsdk-extensions/tests/lifecycles.worker.test.ts +6 -0
  28. package/node_modules/@flickfyi/rwsdk-extensions/tests/package.test.js +22 -0
  29. package/package.json +1 -1
@@ -5,18 +5,18 @@ transforming...
5
5
  rendering chunks...
6
6
  
7
7
  [unplugin:dts] Start generate declaration files...
8
- [unplugin:dts] Declaration files built in 5485ms.
8
+ [unplugin:dts] Declaration files built in 6305ms.
9
9
  
10
10
  computing gzip size...
11
11
  dist/assertion.js 0.09 kB │ gzip: 0.08 kB
12
12
  dist/testing.js 0.88 kB │ gzip: 0.47 kB
13
13
  dist/verify-assertion-BgFGK36e.js 5.64 kB │ gzip: 2.01 kB
14
- dist/index.js 8.06 kB │ gzip: 3.30 kB
14
+ dist/index.js 8.09 kB │ gzip: 3.31 kB
15
15
 
16
- ✓ built in 5.57s
17
- [PLUGIN_TIMINGS] JavaScript callbacks ran for 5.5s of this 5.6s build (99%).
16
+ ✓ built in 6.37s
17
+ [PLUGIN_TIMINGS] JavaScript callbacks ran for 6.3s of this 6.4s build (100%).
18
18
  The slowest callbacks, timed inside each callback (the wait before a callback starts is excluded, the time it awaits is included):
19
- - plugin unplugin-dts buildStart (97%, 5.4s, 1 call)
19
+ - plugin unplugin-dts buildStart (97%, 6.1s, 1 call)
20
20
  Additional callback time came from callbacks under 1s.
21
21
  See https://rolldown.rs/reference/InputOptions.checks#bundlertimings for more details.
22
22
  
@@ -116,11 +116,11 @@ async function verifyAttestation(options) {
116
116
  async function verifyCertChain(x5c, trustedRootCerts) {
117
117
  const certs = x5c.map((der) => new x509.X509Certificate(toArrayBuffer(der)));
118
118
  const trustedRoots = trustedRootCerts.map((pem) => new x509.X509Certificate(pem));
119
- const chain = new x509.X509ChainBuilder({ certificates: certs.slice(1) });
119
+ const chain = new x509.X509ChainBuilder({ certificates: [...certs.slice(1), ...trustedRoots] });
120
120
  const leafCert = certs[0];
121
121
  const lastCert = (await chain.build(leafCert)).at(-1);
122
122
  if (!lastCert) throw new Error("Empty certificate chain");
123
- if (!trustedRoots.some((root) => root.subject === lastCert.issuer || root.subject === lastCert.subject)) throw new Error("Certificate chain does not terminate at a trusted root");
123
+ if (!trustedRoots.some((root) => equalBytes(new Uint8Array(root.rawData), new Uint8Array(lastCert.rawData)))) throw new Error("Certificate chain does not terminate at a trusted root");
124
124
  }
125
125
  /**
126
126
  * Extract the nonce from the Apple attestation credential certificate.
@@ -137,7 +137,9 @@ async function verifyCertChain(
137
137
  const trustedRoots = trustedRootCerts.map(pem => new x509.X509Certificate(pem))
138
138
 
139
139
  // Create certificate chain
140
- const chain = new x509.X509ChainBuilder({ certificates: certs.slice(1) })
140
+ const chain = new x509.X509ChainBuilder({
141
+ certificates: [...certs.slice(1), ...trustedRoots],
142
+ })
141
143
 
142
144
  // Build and verify chain from leaf to root
143
145
  const leafCert = certs[0]
@@ -149,8 +151,8 @@ async function verifyCertChain(
149
151
  throw new Error('Empty certificate chain')
150
152
  }
151
153
 
152
- const isTrusted = trustedRoots.some(
153
- root => root.subject === lastCert.issuer || root.subject === lastCert.subject
154
+ const isTrusted = trustedRoots.some(root =>
155
+ equalBytes(new Uint8Array(root.rawData), new Uint8Array(lastCert.rawData))
154
156
  )
155
157
 
156
158
  if (!isTrusted) {
@@ -1,7 +1,14 @@
1
+ // eslint-disable-next-line eslint-plugin-import/no-unassigned-import
2
+ import 'reflect-metadata'
3
+
1
4
  import { Buffer } from 'node:buffer'
2
5
 
3
- import { describe, expect, it } from 'vitest'
6
+ import { X509Certificate, X509CertificateGenerator } from '@peculiar/x509'
7
+ import is from '@sindresorhus/is'
8
+ import { decode, encode } from 'cborg'
9
+ import { afterEach, describe, expect, it, vi } from 'vitest'
4
10
 
11
+ import { APPLE_APP_ATTESTATION_ROOT_CA_PEM } from '~/internal/apple-root-certs'
5
12
  import { verifyAttestation } from '~/verify-attestation'
6
13
 
7
14
  import fixtures from './fixtures/real-device-attestations.json'
@@ -12,6 +19,7 @@ const swiftAttestationFixture = Buffer.from(
12
19
  )
13
20
 
14
21
  describe('verifyAttestation', () => {
22
+ afterEach(() => vi.useRealTimers())
15
23
  it.each(fixtures)('verifies a real Apple attestation: $name', async fixture => {
16
24
  await expect(
17
25
  verifyAttestation({
@@ -23,6 +31,64 @@ describe('verifyAttestation', () => {
23
31
  ).resolves.toMatchObject({ keyId: fixture.attestation.keyId })
24
32
  })
25
33
 
34
+ it.each(fixtures)('accepts expired Apple leaf certificates: $name', async fixture => {
35
+ vi.useFakeTimers({ toFake: ['Date'] })
36
+ vi.setSystemTime(new Date('2030-01-01T00:00:00Z'))
37
+ await expect(
38
+ verifyAttestation({
39
+ appId: fixture.appId,
40
+ attestation: Buffer.from(fixture.attestation.data, 'base64'),
41
+ challenge: Buffer.from(fixture.attestationChallenge, 'base64'),
42
+ expectedKeyId: fixture.attestation.keyId,
43
+ })
44
+ ).resolves.toMatchObject({ keyId: fixture.attestation.keyId })
45
+ })
46
+
47
+ it.each([
48
+ 'missing intermediate',
49
+ 'invalid intermediate signature',
50
+ 'forged Apple name',
51
+ ])('rejects an untrusted chain: %s', async mutation => {
52
+ const fixture = fixtures[0]
53
+ const attestation: unknown = decode(Buffer.from(fixture.attestation.data, 'base64'))
54
+ if (
55
+ !is.plainObject(attestation) ||
56
+ !is.plainObject(attestation.attStmt) ||
57
+ !is.array(attestation.attStmt.x5c) ||
58
+ !attestation.attStmt.x5c.every(is.uint8Array)
59
+ ) {
60
+ throw new Error('Invalid test fixture')
61
+ }
62
+ const certificates = attestation.attStmt.x5c
63
+ if (mutation === 'missing intermediate') {
64
+ attestation.attStmt.x5c = certificates.slice(0, 1)
65
+ } else if (mutation === 'invalid intermediate signature') {
66
+ const intermediate = certificates[1]
67
+ intermediate[intermediate.length - 1] =
68
+ (intermediate[intermediate.length - 1] + 1) % 256
69
+ } else {
70
+ const root = new X509Certificate(APPLE_APP_ATTESTATION_ROOT_CA_PEM)
71
+ const keys = await crypto.subtle.generateKey(
72
+ { name: 'ECDSA', namedCurve: 'P-256' },
73
+ true,
74
+ ['sign', 'verify']
75
+ )
76
+ const impostor = await X509CertificateGenerator.createSelfSigned({
77
+ keys,
78
+ name: root.subject,
79
+ signingAlgorithm: { hash: 'SHA-256', name: 'ECDSA' },
80
+ })
81
+ attestation.attStmt.x5c = [new Uint8Array(impostor.rawData)]
82
+ }
83
+ await expect(
84
+ verifyAttestation({
85
+ appId: fixture.appId,
86
+ attestation: encode(attestation),
87
+ challenge: Buffer.from(fixture.attestationChallenge, 'base64'),
88
+ })
89
+ ).rejects.toThrow('Certificate chain does not terminate at a trusted root')
90
+ })
91
+
26
92
  it('verifies a Swift App Attest attestation bound to the identity key ID', async () => {
27
93
  await expect(
28
94
  verifyAttestation({
@@ -5,15 +5,15 @@ transforming...
5
5
  rendering chunks...
6
6
  
7
7
  [unplugin:dts] Start generate declaration files...
8
- [unplugin:dts] Declaration files built in 4460ms.
8
+ [unplugin:dts] Declaration files built in 6352ms.
9
9
  
10
10
  computing gzip size...
11
11
  dist/index.js 173.72 kB │ gzip: 29.36 kB
12
12
 
13
- ✓ built in 4.57s
14
- [PLUGIN_TIMINGS] JavaScript callbacks ran for 4.5s of this 4.6s build (98%).
13
+ ✓ built in 6.58s
14
+ [PLUGIN_TIMINGS] JavaScript callbacks ran for 6.4s of this 6.5s build (97%).
15
15
  The slowest callbacks, timed inside each callback (the wait before a callback starts is excluded, the time it awaits is included):
16
- - plugin unplugin-dts buildStart (93%, 4.2s, 1 call)
16
+ - plugin unplugin-dts buildStart (92%, 6.0s, 1 call)
17
17
  Additional callback time came from callbacks under 1s.
18
18
  See https://rolldown.rs/reference/InputOptions.checks#bundlertimings for more details.
19
19