@flickfyi/flkr 0.0.153 → 0.0.156
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.
- package/node_modules/@flickfyi/attestation/.turbo/turbo-build.log +5 -5
- package/node_modules/@flickfyi/attestation/dist/index.js +2 -2
- package/node_modules/@flickfyi/attestation/src/verify-attestation.ts +5 -3
- package/node_modules/@flickfyi/attestation/tests/verify-attestation.test.ts +67 -1
- package/node_modules/@flickfyi/biscuit/.turbo/turbo-build.log +4 -4
- package/node_modules/@flickfyi/core/.turbo/turbo-build.log +86 -85
- package/node_modules/@flickfyi/http-message-sig/.turbo/turbo-build.log +6 -6
- package/node_modules/@flickfyi/http-message-sig/dist/index.js +683 -3
- package/node_modules/@flickfyi/http-message-sig/package.json +3 -2
- package/node_modules/@flickfyi/http-message-sig/src/build.ts +3 -1
- package/node_modules/@flickfyi/http-message-sig/tests/build.spec.ts +6 -0
- package/node_modules/@flickfyi/identity/.turbo/turbo-build.log +7 -6
- package/node_modules/@flickfyi/identity/README.md +36 -0
- package/node_modules/@flickfyi/identity/dist/index.d.ts +9 -6
- package/node_modules/@flickfyi/identity/dist/index.js +1 -1
- package/node_modules/@flickfyi/identity/src/types.ts +8 -3
- package/node_modules/@flickfyi/identity/src/verify-attestation.ts +10 -6
- package/node_modules/@flickfyi/identity/tests/fixtures/expired-attestation.json +9 -0
- package/node_modules/@flickfyi/identity/tests/supplied-attestation.test.ts +104 -0
- package/node_modules/@flickfyi/lens/.turbo/turbo-build.log +4 -4
- package/node_modules/@flickfyi/photon/.turbo/turbo-build.log +9 -3
- package/node_modules/@flickfyi/rwsdk-extensions/.turbo/turbo-build.log +4 -4
- package/package.json +1 -1
|
@@ -5,18 +5,18 @@ transforming...
|
|
|
5
5
|
rendering chunks...
|
|
6
6
|
[32m
|
|
7
7
|
[36m[unplugin:dts][32m Start generate declaration files...[39m
|
|
8
|
-
[32m[36m[unplugin:dts][32m Declaration files built in
|
|
8
|
+
[32m[36m[unplugin:dts][32m Declaration files built in 7196ms.
|
|
9
9
|
[39m
|
|
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.
|
|
14
|
+
dist/index.js 8.09 kB │ gzip: 3.31 kB
|
|
15
15
|
|
|
16
|
-
[32m✓ built in
|
|
17
|
-
[33m[33m[PLUGIN_TIMINGS] [0mJavaScript callbacks ran for
|
|
16
|
+
[32m✓ built in 7.29s[39m
|
|
17
|
+
[33m[33m[PLUGIN_TIMINGS] [0mJavaScript callbacks ran for 7.2s of this 7.3s build (99%).
|
|
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%,
|
|
19
|
+
- plugin unplugin-dts buildStart (97%, 7.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
|
[39m
|
|
@@ -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) =>
|
|
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({
|
|
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
|
-
|
|
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 {
|
|
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
|
[32m
|
|
7
7
|
[36m[unplugin:dts][32m Start generate declaration files...[39m
|
|
8
|
-
[32m[36m[unplugin:dts][32m Declaration files built in
|
|
8
|
+
[32m[36m[unplugin:dts][32m Declaration files built in 5665ms.
|
|
9
9
|
[39m
|
|
10
10
|
computing gzip size...
|
|
11
11
|
dist/index.js 173.72 kB │ gzip: 29.36 kB
|
|
12
12
|
|
|
13
|
-
[32m✓ built in
|
|
14
|
-
[33m[33m[PLUGIN_TIMINGS] [0mJavaScript callbacks ran for
|
|
13
|
+
[32m✓ built in 5.91s[39m
|
|
14
|
+
[33m[33m[PLUGIN_TIMINGS] [0mJavaScript callbacks ran for 5.7s of this 5.9s 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 (
|
|
16
|
+
- plugin unplugin-dts buildStart (90%, 5.3s, 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
|
[39m
|