#Apple Attest, Transaction Middleware Failing verify the public Key + Signature Buff on TS Middleware

895 messages ยท Page 1 of 1 (latest)

icy cosmos
#

would hugely value some assistance because im very close to slamming my pc

#

Im building a startup and im sorta pretty technical but im like actually facepalming over some encryption that I simply cannot get to work.

(for reference its to do with a typescript middleware failing to authenticate transactional requests from apple app attest)
not the middleware designated for apple attest registration, that works fine and registers both apple and android devices in my database with their public keys for continued transactional auth.

The issue is a failure with either the publicKeyPem, or the derSignatureBuffer. And its failing when applied to the crypto.verify().

    pidLogger(SIGNATURE_ALGORITHM, dataToVerify, publicKeyPem, derSignatureBuffer);
    // d) Verify (Authenticity)
    verified = crypto.verify(
      SIGNATURE_ALGORITHM,
      dataToVerify,
      publicKeyPem,
      derSignatureBuffer
    );

is failing for my apple clients, works fine for my android clients
been back and forth with ever LLM in existence to see if they can solve it, and they just go around in circles trying solutions which inevitably are bad or suggest system re-design for no good reason.

The entire file is quite large, and so is the client side apple implementation, so I'm hesitant to post it all at once as its very large. So for now I'll include only the middleware aspect that is strictly relevant. and the error logging.

#

im not even sure why its failing

#

ive done so many different attempts with der conversion and not-der conversion

#

for a more critical analysis, I'll provide 2 logged outputs from the server, the first is an android client going through crypto.verify() and it passes fine, the second is the apple client.

These logs are of the entered data to crypto.verify()

[2025-12-10T08:04:45.236Z] [PID-1] [appRequest.js] LOG sha256 <Buffer 2d da bf 62 29 a0 f4 1b 6d 29 18 e8 f6 2a c9 21 98 bb 15 aa 82 2f 82 77 98 38 e6 1a 90 f9 2a ba> -----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuvmMezVWhDKzzzmYd3CNbnvadthImoZ4TduMEy6e3fxa6pgd2LoWvYq3BvPRDtrmzYbKvvmLI8smaslVgSkk4g==
-----END PUBLIC KEY----- <Buffer 30 44 02 20 16 5e da 48 77 8d d2 cb 64 a3 28 f2 22 cf 7e 03 82 c2 d7 25 81 fe 01 4d 7c 8e ab d0 76 35 85 aa 02 20 40 da f2 ad b8 d4 6f 4b 54 d0 6c 4b ... 20 more bytes>

so above is android, and that verified fine.
Beneath is apple, and it got rejected.

[2025-12-10T08:05:23.861Z] [PID-1] [appRequest.js] LOG sha256 <Buffer 90 a9 f9 aa 0f 46 0f 6f 92 78 76 40 59 b3 fb 05 7b a1 7d a8 0e 77 a9 09 30 3e 1b b7 c0 71 af bf> -----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEessoJLJkPaWnWK3svFjV9x3Q06Vi
kgZqsgMZ1WE+sOFTJ5slz1/Kg27iHEzEWfvzgTATIhRxdDpTgzJ/VkP6WQ==
-----END PUBLIC KEY----- <Buffer 30 45 02 21 00 be b9 85 db 24 f7 9b 2c 3b 98 cf 32 79 99 51 5c 04 ee 89 28 c0 81 aa e4 35 86 21 f8 50 b9 87 f4 02 20 0f 79 78 ac be 62 1e 74 d5 eb 78 ... 21 more bytes>

the logging that triggers those is as follows:

    pidLogger(SIGNATURE_ALGORITHM, dataToVerify, publicKeyPem, derSignatureBuffer);
    // d) Verify (Authenticity)
    verified = crypto.verify(
      SIGNATURE_ALGORITHM,
      dataToVerify,
      publicKeyPem,
      derSignatureBuffer
    );
#

i can see the derSignatureBuffer for apple has an extra byte. but apart from that, im so confused. (ive never used crypto.verify()) before.

outer oyster
#

are you using a proper CryptoKey? and isn't the signature verify(algorithm, key, signature, data)

#

or is this not subtle crypto

#

oh you're probably using the older nodejs crypto?

icy cosmos
#

im not sure honestly

#

wait

#

let me send the more specific bit from the server

abstract grove
#

the answer to @outer oyster's question would depend on where that crypto variable comes from (i am guessing you're not using the web APIs though and are instead using node's crypto module)

icy cosmos
#
import crypto from 'crypto';
outer oyster
#

yeah pretty sure this is old node crypto the argument order matches. DER length is a red-herring

icy cosmos
#

im new to ts massively

#

sorry

#

like very new

abstract grove
icy cosmos
#

okay so basically the core issue im having

#

is that i just cant seem to verify

#

the dersig

#

and the publickeypem

#

and sometimes its the publickey thats failing it i think

outer oyster
#

keyBodyForPem = formatBase64ForPem(rawKeyBody); why do you do this differently for ios?

#

its unlikely the crypto.verify routines are broken on one os but not another

icy cosmos
#

because the ios instance isnt base64 i think when it comes through

#

or is

#

i cant remember ive switched it so many times

icy cosmos
#

ill re - run right now

#

and show you the logging basically

#

of what comes in on an apple vs android instance

#

sorry im naff at explaining this its just so messy

#

its running now

#

android is just slow to talk to the server

outer oyster
#

on fundamental difference is the PEM key has a newline break in it on iOS?

#

should be fine and normal, but its different

#

would it be possible to use the same public key on both for testing purposes? perhaps the signature is being signed by a different key on iOS

icy cosmos
#

[2025-12-10T17:34:34.993Z] [PID-1] [appRequest.js] WARN Device UDID not found or public key missing: 7889a985-2646-45e3-9c88-ff221117ed78. Triggering re-attestation.
[2025-12-10T17:34:34.998Z] [PID-1] [server.js] REQ 77.103.192.136 | POST | /api/v1/device/alive | 401 | 4.913 ms | UA: UnknownBrowser on Android 
[2025-12-10T17:34:38.265Z] [PID-1] [server.js] REQ 77.103.192.136 | POST | /api/v1/regcomms/devicekeyregister | 201 | 652.969 ms | UA: UnknownBrowser on Android 
[2025-12-10T17:34:38.568Z] [PID-1] [appRequest.js] LOG [VERIFY DEBUG] Platform: Android | Public Key (Body Start): MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJNiVwhhsCmH1AdOeK94JWuA0MsM0VBf5hri6OhHKqY1TENAcPATT8Yak7itXraQ217KvIG7QqchK2+4XfYuG8A== | UDID: e44d54cf-cc42-47cb-8a3b-c0e1fe163f4a
[2025-12-10T17:34:38.568Z] [PID-1] [appRequest.js] LOG [AndroidHandler] Using string concatenation hashing.
[2025-12-10T17:34:38.568Z] [PID-1] [appRequest.js] LOG [AndroidHandler] Processing signature length: 71
[2025-12-10T17:34:38.568Z] [PID-1] [appRequest.js] LOG sha256 <Buffer 45 d4 ba b4 54 a4 d8 dd 0d 21 ca 75 73 37 de a9 95 76 d1 d0 dd 67 24 05 32 d5 0e 48 f0 27 2b 62> -----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJNiVwhhsCmH1AdOeK94JWuA0MsM0VBf5hri6OhHKqY1TENAcPATT8Yak7itXraQ217KvIG7QqchK2+4XfYuG8A==
-----END PUBLIC KEY----- <Buffer 30 45 02 20 45 19 06 f2 a0 85 e5 2a 4e 1c 34 4b 83 00 7f 46 49 6d 70 ea da 90 5c da d8 ab 93 18 83 37 c9 4c 02 21 00 d7 f8 24 ec 8b f9 f4 8b 60 3c 90 ... 21 more bytes>
[2025-12-10T17:34:38.569Z] [PID-1] [appRequest.js] LOG [VERIFY] Platform: Android | Hash: 45d4bab454a4d8dd0d21ca757337dea99576d1d0dd67240532d50e48f0272b62 | Result: true
[2025-12-10T17:34:38.575Z] [PID-1] [server.js] REQ 77.103.192.136 | POST | /api/v1/device/alive | 200 | 8.006 ms | UA: UnknownBrowser on Android 

that above is an ANDROID connection to the server, you can see the whole endpoint flow

#

the endpoint hits are listed BENEATH the logic thats run because of them

icy cosmos
#

and you can see from that log ultimately the result is TRUE on the verify

#

and it sends back a 200 and is happy

outer oyster
# icy cosmos i dont think so

you reported for apple above

[2025-12-10T08:05:23.861Z] [PID-1] [appRequest.js] LOG sha256 <Buffer 90 a9 f9 aa 0f 46 0f 6f 92 78 76 40 59 b3 fb 05 7b a1 7d a8 0e 77 a9 09 30 3e 1b b7 c0 71 af bf> -----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEessoJLJkPaWnWK3svFjV9x3Q06Vi
kgZqsgMZ1WE+sOFTJ5slz1/Kg27iHEzEWfvzgTATIhRxdDpTgzJ/VkP6WQ==
-----END PUBLIC KEY----- <Buffer 30 45 02 21 00 be b9 85 db 24 f7 9b 2c 3b 98 cf 32 79 99 51 5c 04 ee 89 28 c0 81 aa e4 35 86 21 f8 50 b9 87 f4 02 20 0f 79 78 ac be 62 1e 74 d5 eb 78 ... 21 more bytes>
icy cosmos
#

sorry yes then

#

im so fed up of this problem icl im sorry

#

yes

#

your right both have the newline break

#

both

#

and that is the apple log

#

when an apple client hits it

#

and there, the /alive endpoint returns 401 because the verify fails

outer oyster
icy cosmos
#

erm

outer oyster
#

so somethings different about how the PEM files arrive

icy cosmos
#
        {
          "_id": "6939af2e86137db0b7b08f7c",
          "deviceUDID": "e44d54cf-cc42-47cb-8a3b-c0e1fe163f4a",
          "__v": 0,
          "createdAt": "2025-12-10T17:34:38.259Z",
          "deviceIp": "77.103.192.136",
          "lastSeenAt": "2025-12-10T17:34:38.569Z",
          "manufacturer": "Google",
          "model": "Pixel 3 XL",
          "osVersion": "31",
          "platform": "android",
          "publicKeyPem": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJNiVwhhsCmH1AdOeK94JWuA0MsM0VBf5hri6OhHKqY1TENAcPATT8Yak7itXraQ217KvIG7QqchK2+4XfYuG8A==",
          "serverIp": "77.103.192.136",
          "CACHED": "MATCH"
        },
        {
          "apple": {
            "keyId": "lXWIuev+bjTAfWgD9vmwvP1i04A8HlWNI0Rw+rILiog=",
            "challengeNonce": "OBlGHy8JNz4NX5Q6ECE44IHnC2BmXhfSyWFWr_dYwb8"
          },
          "_id": "6939af9b86137db0b7b08fb1",
          "deviceUDID": "773fdef9-623d-4ef7-a87b-b38efa7add2f",
          "__v": 0,
          "createdAt": "2025-12-10T17:36:27.702Z",
          "deviceIp": "77.103.192.136",
          "lastSeenAt": "2025-12-10T17:36:27.702Z",
          "manufacturer": "Apple",
          "model": "iPhone",
          "osVersion": "18.7.2",
          "platform": "apple",
          "publicKeyPem": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEVy8yuxOqkRIcIHZz8HKRvPxWdSiEDEPDS1TCcn9N3NOX9pJS0Z1u7wjzzW6ozsV6JchfrRmJC1/KifuYYmRwKQ==",
          "serverIp": "77.103.192.136",
          "CACHED": "MATCH"
        },

#

server storage of the 2 devices

#

idc about the exposed ip's honestly

#

that storage is triggered by the /devicekeyregister

outer oyster
#

I mean look at your logs

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJNiVwhhsCmH1AdOeK94JWuA0MsM0VBf5hri6OhHKqY1TENAcPATT8Yak7itXraQ217KvIG7QqchK2+4XfYuG8A==
-----END PUBLIC KEY-----

versus on iOS

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEVy8yuxOqkRIcIHZz8HKRvPxWdSiE
DEPDS1TCcn9N3NOX9pJS0Z1u7wjzzW6ozsV6JchfrRmJC1/KifuYYmRwKQ==
-----END PUBLIC KEY----- 
icy cosmos
#

yes sorry yes

#

hm

#

im thinking

#

that its something to do with base 64 URLSAFE vs non urlsafe

#

or something along those lines

#

ive been confused by it for ages today

#

AH YES

outer oyster
#

pem doesn't use urlsafe it uses mime, iirc

icy cosmos
#

i see what your saying

#

there is a space

#

between the iE and the DEPD

#

hm

outer oyster
#

it's probably not the cause, but it could be?

icy cosmos
#

i dont think it is the cause honestly

#

wait

#

let me just check and see why thats even occuring

outer oyster
#

yeah its odd

icy cosmos
#

because the registraion endpoint saves it in the db without a space

#

as you see in the json above

#

but then clearly when it comes back for transactional requests it is being processed / sent or whatever with a gap

#

    const rawKeyBody = deviceRecord.publicKeyPem!.trim().replace(/\s/g, '');
    let keyBodyForPem = rawKeyBody; // Initialize with the raw body


    if (isIos) {
      keyBodyForPem = formatBase64ForPem(rawKeyBody);


      pidLogger.log(`[VERIFY DEBUG] iOS PEM FORMATTER APPLIED. First 64 chars: \n${keyBodyForPem.substring(0, 64)}...`);
    }
    const publicKeyPem = [
      '-----BEGIN PUBLIC KEY-----',
      keyBodyForPem,
      '-----END PUBLIC KEY-----',
    ].join('\n');
#

yep i need to remove the apple specific formatting

#

its clearly resulting in the the key having that linebreak

#

but i dont think

#

that its the cause

#

re-running the server and clients

outer oyster
#

how is iOS delivering its public key differently to you?

#

can you hard code the signature and PEM to be the working ones from android?

#

just to check the verification routines

icy cosmos
#

thats a good idea

outer oyster
#

oh you need to hard code data too

icy cosmos
#

im deving on windows and a mac mini which i have to switch my inputs for so ill try that in a second.

[2025-12-10T17:47:14.131Z] [PID-1] [appRequest.js] LOG sha256 <Buffer b3 f2 35 4b 9b 2e 43 63 f7 79 16 45 7f 2f 38 f2 92 8d 58 c5 e9 59 e4 9a f6 d3 ba 42 57 07 92 04> -----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdsOFsb56E2VQ2Q9tScmG6Y402Q5zNS8Q+qCsEc26IpcYnPvaMN91pzpkuvK+xpqCwmuA92DfTPeuYHvrqH4oTw==
-----END PUBLIC KEY----- <Buffer 30 45 02 21 00 99 08 32 35 af 95 8d cf ec 2d 24 3a 8b 09 a5 45 7b 45 b0 58 83 db 31 fc 67 ad 92 56 13 cb fc 9b 02 20 75 3c 47 d0 0a c6 d7 d4 8c 96 ff ... 21 more bytes>
#

thats now the apple printout before we go into the crypto.verify

icy cosmos
#

i only just comprehend the entire app attest flow

outer oyster
#

isn't this a http endpoint, don't you get these from request params?

icy cosmos
#

its so confusing to me though as apple is embedding the public key in the attestation object when it sends it

#

whereas android is sending it as a request object on the body

#

at least i think

#

im just so confused its mind blowing

#

because that log you see above

#

with the public key looking the same

#

is the public key from the database

#

not the public key thats just come from the client

#

because the client doesnt send the public key

#

it sends the signature

#

and the data to verify

#

to my understanding

#

same with android

outer oyster
#

so this appRequestHandler is running on iOS/Android or is it receiving requests from another client?

What does dumping const deviceRecord = await DeviceManager.get(deviceUDID); look like

outer oyster
icy cosmos
icy cosmos
#

so dumping deviceRecord would just dump one of those with the given deviceUDID

#

and the formatting of the public key that we just made the same as when android hits it, is the formatting of the servers public key for that device

#

ultimately its something to do with the signature that the clients are sending is not right for apple but right for android

outer oyster
#

so then if the deviceRecord already has a properly formatted publicKeyPem, why are you doing this

    const rawKeyBody = deviceRecord.publicKeyPem!.trim().replace(/\s/g, '');
    let keyBodyForPem = rawKeyBody; // Initialize with the raw body
icy cosmos
#

when it goes into the crypto.verify function

icy cosmos
#

it was because

#

i thought i needed to do things differently for apple i think

#

but it might not be needed now

#

for context ive been fighting this issue for 4 days

#

literally

#

so ive been through so many different iterations

#

deviceRecord.publicKeyPem
that should be all i need in terms of keyBodyForPem

#

i think

#

and just put the tags around it

#

so ---BEGIN and end

outer oyster
#
    const rawKeyBody = deviceRecord.publicKeyPem!.trim().replace(/\s/g, '');
    let keyBodyForPem = rawKeyBody;
    if (isIos) {
      keyBodyForPem = formatBase64ForPem(rawKeyBody);
    }

I think you can rip this out

icy cosmos
#

alright let me give that a shot

outer oyster
icy cosmos
#

yes but i do need to substitute the publicKeyPem from deviceRecord into the --begin end thing now

#

directly

#

as opposed to running that formatBase64 on it

outer oyster
icy cosmos
#

and all that

#

testing now on android first to ensure its not broken

#

and then ill run the apple client

#

i appreciate its kind of messy having 2 systems on the smae middleware

#

but im trying to simplify it as much as possible

#

[2025-12-10T17:57:28.858Z] [PID-1] [appRequest.js] WARN Device UDID not found or public key missing: 554a4b53-fecc-4c1a-87a0-48bc14d05484. Triggering re-attestation.
[2025-12-10T17:57:28.863Z] [PID-1] [server.js] REQ 77.103.192.136 | POST | /api/v1/device/alive | 401 | 4.730 ms | UA: UnknownBrowser on Android
[2025-12-10T17:57:30.869Z] [PID-1] [server.js] REQ 77.103.192.136 | POST | /api/v1/regcomms/devicekeyregister | 201 | 397.977 ms | UA: UnknownBrowser on Android
[2025-12-10T17:57:31.129Z] [PID-1] [appRequest.js] LOG [VERIFY DEBUG] Platform: Android | Public Key (Body Start): MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAErW/3Bo8tJa6hbYkUoRHGTe+wOhjB4LuJTVQ0gqpLvKRaO+rmtLs6c2AVOhyZPFsKxcUw+GCGh4Qj/RIS07m96Q== | UDID: 26e818d4-7359-4a1e-a47c-030024ddb19e
[2025-12-10T17:57:31.129Z] [PID-1] [appRequest.js] LOG [AndroidHandler] Using string concatenation hashing.
[2025-12-10T17:57:31.129Z] [PID-1] [appRequest.js] LOG [AndroidHandler] Processing signature length: 70
[2025-12-10T17:57:31.129Z] [PID-1] [appRequest.js] LOG sha256 <Buffer b7 b1 d4 bd ff ba 20 06 58 94 98 13 64 bf a7 44 8e 34 6e fd 0d e3 2e ee 53 7b 2f dc f3 70 0c f6> -----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAErW/3Bo8tJa6hbYkUoRHGTe+wOhjB4LuJTVQ0gqpLvKRaO+rmtLs6c2AVOhyZPFsKxcUw+GCGh4Qj/RIS07m96Q==
-----END PUBLIC KEY----- <Buffer 30 44 02 20 1c cd ee 2c 42 57 d6 29 05 0b 23 fd 7c 15 1c 22 00 fe 9f b1 6c 66 f1 bf bf 89 df b8 92 f2 32 b2 02 20 47 d1 48 39 c5 37 6f b8 e7 f0 5f 2f ... 20 more bytes>
[2025-12-10T17:57:31.130Z] [PID-1] [appRequest.js] LOG [VERIFY] Platform: Android | Hash: b7b1d4bdffba20065894981364bfa7448e346efd0de32eee537b2fdcf3700cf6 | Result: true
[2025-12-10T17:57:31.138Z] [PID-1] [server.js] REQ 77.103.192.136 | POST | /api/v1/device/alive | 200 | 8.705 ms | UA: UnknownBrowser on Android

#

android is happy

#

apple is still not

outer oyster
#

what is this

Using raw buffer concatenation hashing (URL-Safe to Standard Decode).

#

this is implemented on the client side, not the server side?

icy cosmos
#

nope thats server side, all those logs are nodejs

#

let me find it for you

outer oyster
#

ah found it, reading source code on discord is hard

icy cosmos
#

yeah i know im so sorry man

#

you guys are genuinely good at helping idk how id do it

#

id have to request the whole project

#

and put it in my editor

outer oyster
#

what's androidHandler look like?

icy cosmos
#

for reference this is the structure

#

and ill send over androidHandler now

#

const androidHandler: PlatformHandler = {
  /**
  * Android client hashes the string: Base64(encryptedPayload) + ":" + Base64(nonce)
  */
  getPayloadHash: (encryptedPayload: string, nonce: string): Buffer => {
    pidLogger.log('[AndroidHandler] Using string concatenation hashing.');
    const combinedData = `${encryptedPayload}:${nonce}`;
    return crypto.createHash(SIGNATURE_ALGORITHM).update(combinedData, 'utf-8').digest();
  },

  /**
  * Android client sends raw 64-byte ECDSA (R|S) or pre-DER-encoded signature.
  */
  processSignature: (rawSignatureBuffer: Buffer): Buffer | null => {
    pidLogger.log(`[AndroidHandler] Processing signature length: ${rawSignatureBuffer.length}`);
    if (rawSignatureBuffer.length === 64) {
      // Raw R|S, convert to DER
      return rawEcdsaSigToDer(rawSignatureBuffer);
    } else if (rawSignatureBuffer.length > 64 && rawSignatureBuffer.length < 100 && rawSignatureBuffer[0] === 0x30) {
      // Already DER format
      return rawSignatureBuffer;
    }
    return null;
  },
};
#

thats the androidHandler

#

in appRequest.ts

#

the transactional middleware

outer oyster
#

what is the client app? is it something you control?

icy cosmos
#

yes

outer oyster
#

just trying to figure out why these use different schemes

icy cosmos
#

its possible ive messed up my apple client so its sending slightly bad stuff

#

because apple loads everything into an attestation object

outer oyster
#

so you're generating the signature yourself? it's not some OS-level process?

icy cosmos
#

its difficult bcuz im working on 1 monitor and replugging in every time to switch to the mac mini xcode

outer oyster
#

can you show the code for that

icy cosmos
#

ill get the client side stuff for you

outer oyster
#

I'd tell you that usb switches are cheap and easy, but that doesn't help you right now

icy cosmos
#

i have one it broke tho

#

my monitor gets pixels from mac mini through it

#

so annoying

#

im gonna switch to the mac mini and copy it across from github

#

okay right

#

here is the Server.swift

#

at least the bit that matters

#

and is used for ongoing transactional requests

#

ive worked with swift apps but never had to build server - client apps before

#

im sorry its all so much

#

its got pretty high security requirements

#
    let signatureB64 = try await deviceIdManager.sign(dataToSign: dataToSign)

    // 6. Build Transport Body
    let transportBody = AppRequestBody(
      encryptedPayload: encryptedPayloadB64,
      nonce: nonceB64,
      signature: signatureB64,
      deviceUDID: deviceUDID
    )
#

okay

#

ill get the sign() for you

outer oyster
#

like why not use the same technique for both

icy cosmos
#

id love them to be the same

#

but apparently in apple it should be done differently

#

    func sign(dataToSign: Data) async throws -> String {
        
        // Use generateAssertion to get the signature
        let keyId = credentials.publicKeyIdentifier
        guard !keyId.isEmpty else { throw KeyManagerError.signingFailed }
        
        // The generateAssertion method returns the **Assertion Object**, which is a signed
        // PKCS#7 message containing the ECDSA signature. This is what must be sent.
        let assertionData = try await generateAssertion(keyId: keyId, clientDataHash: dataToSign)
        
        // The server is expecting this full object, which corresponds to the 141 bytes length,
        // and must have verification logic to extract the raw signature from it.
        return assertionData.base64EncodedString()
    }
#
    private func generateAssertion(keyId: String, clientDataHash: Data) async throws -> Data {
        try await withCheckedThrowingContinuation { continuation in
            DCAppAttestService.shared.generateAssertion(keyId, clientDataHash: clientDataHash) { assertionData, error in
                if let error = error {
                    continuation.resume(throwing: error)
                    return
                }
                guard let assertion = assertionData else {
                    continuation.resume(throwing: KeyManagerError.signingFailed)
                    return
                }
                continuation.resume(returning: assertion)
            }
        }
    }
    
#

and theres the asserton generator

outer oyster
#

I think you have everything you need to match android

icy cosmos
#

id love to match android

#

relaly ill explain breifly why its all so complex

#

im building an auth system which authenticates users based on the fact that they HAVE their device

#

so like

#

your device is your password

#

and so when they loose their private key on their device

#

or their deviceUDID they loose their account

#

(intentional)

#

and because of that premise of binding a device to a device record in my db via a public key system

#

i was advised that i needed to use this complex attestation thing

#

going forwards

#

for the transactional middleware for apple

#

this is my firs time even using the devicecheck service which just makes it all so much more complex

#

but to my understanding

#

the reason we use the DC service every transactional request

#

is to that the user doesnt register on a phone, and then run off with the public key to a different machine (after intercepting their network requests) and use a PC with that public key

#

and my server believe because the reqs have that public key signature, that the computer is a phone

#

because we dont bother checking for assertion

#

at least thats a high level explanation of what im trying to mitigate

#

its all a bit foggy deeper in

outer oyster
#

I'm making sure this'll actually work

icy cosmos
#

im trying to build a system where we can trust the device's gps data (within reason) as a security signal

outer oyster
#

why do you encrypt the body, isn't signature enough?

icy cosmos
#

i just want to be sure rlly.

#

its like highly sensitive payload data

#

that will be being sent

outer oyster
#

yeah but over https, right?

icy cosmos
#

and i dont want either the USER or an attacker to tamper

#

yes but a user can tamper it in their network

#

so we encrypt with a company public key on the devices

outer oyster
#

with the signature, there's no danger of tampering. The signature wont verify if its been tampered with, whether the body is plaintext or encrypted

#

and its over https so its not like the data is leaked otherwise. But it doesn't really matter, it just makes it more complex, more places to go wrong

icy cosmos
#

but cant the user technically manipulate the data

#

if we dont encrypt at that point

outer oyster
#

nope. If they modify the body data, then the signature wont verify it, because the signature is built to verify only the exact string pre-tampering

icy cosmos
#

so your saying my company public key encryption part isnt needed? or the other AES ecnryption layer?

outer oyster
#

the AES encryption layer. You need the public key encryption for signature verification

icy cosmos
#

just to clarify theres 2 public key things going on

outer oyster
#

I'm saying you could skip

    let (encryptedPayloadB64, nonceB64) = try CryptoManager.encrypt(
      jsonObject: decryptedPayload,
      key: credentials.serverSymmetricKey
    )
#

deviceIdManager.sign(dataToSign: dataToSign) this is the one that really matters

icy cosmos
#

the device generates private key to identify itself and send the public key to ths erver and thats how its identified (with the deviceUDID) and then theres also a company public key which comes with the app and signs all of the requests for my server to unencrypt

icy cosmos
#

is that really not necessary??

outer oyster
#

hm, maybe I don't understand the flow

icy cosmos
#

im just so mind boggled honestly

#

been round in loops endlessly

#

instead of changing the apple flow to fit android, why dont i just like idk debug the signature

#

and fix the server to work with the signature properly

outer oyster
#

I was imagining

When a user does initial attestation you get a public key from the device credentials, its unique to that phone and can't be exfiltrated. You store that in your database somewhere

When the app on the phone needs to attest is location, all it needs to do is send a message in plaintext with a signature. You lookup the appropriate public key in your database and verify the message from the phone. Now you know that your app on that exact phone provided the info and nothing else

#

the user nor attackers can tamper with the message once its been signed, that'd cause verification to fail

#

but I might be missing part of what you're trying to do

icy cosmos
#

yes thats kinda the flow, except also EVERY message between client and server is encrypted with company public key, and we also use attest on the initial registration to pass that clients public key generated up to the server

#

and then i think ever message onwards from the client to the server is 1, encrypted with company public key, 2, put in an assertion object thing with a signature or something

outer oyster
#

except also EVERY message between client and server is encrypted with company public key

that's the part I'm not sure is really necessary. You're already using HTTPS transport, which is also public key encryption controlled by your company.

icy cosmos
#

isnt it needed though to prevent network tampering

#

because the client can tamper the HTTPS

#

in their own network

outer oyster
#

sure the client can tamper with https, but if they modify the message then crypto.verify will reject the message as tampered with

icy cosmos
#

and then they can potentially doctor the data like location data

outer oyster
#

you're signing the location data too, right?

#

I assume all that info goes in decryptedPayload

icy cosmos
#

let me check

outer oyster
#

so location and everything else gets signed by the resident credentials

icy cosmos
#

// 2. Build Decrypted Payload

var decryptedPayload: JSONObject = [
  "deviceUDID": deviceUDID,
  "accountId": SecureStorage.load(key: KeychainKeys.accountIdKey),
  "pushNotifKey": SecureStorage.load(key: KeychainKeys.pushNotificationKey),
  "data": data,
  "deviceFingerprint": [
    "manufacturer": "Apple",
    "model": UIDevice.current.model,
    "osVersion": UIDevice.current.systemVersion
  ]
]
outer oyster
#

yeah it just says "data" could be anything

icy cosmos
#

yeah the locatino payload

#

should be included

#

NEXT TO the devicefingerpritn

#

ive accidentally not included it

#

because i updated it recently

outer oyster
#

then you're signing the location payload. If anyone tampers with it after signing, your server will reject the message

icy cosmos
#

but yes it goes in the decryptedPayload

outer oyster
#

regardless of whether or not you encrypt the body in the first place

#

HTTPS prevents third-parties from reading any sensitive data contained therein

icy cosmos
#

yeah look its generated from the location service but then ive forgotten to put it in

#

but yes as you say it should be signed

outer oyster
#

unless you don't trust your server hosts

icy cosmos
#

we send location data on EVERY request irrespective of the purpose of the request

#

and middleware on the server does stuff like fraud prevention with that

outer oyster
#

sure good for logging

icy cosmos
#

ive just not put it in the payload

#

cuz im stupid

#

lmao

outer oyster
#

gps spoofing is a possible attack, its not a gold standard btw, but it is high effort to do that ๐Ÿ˜†

icy cosmos
#

yeah ik

#

its a calculated risk

#

we do biometric authentication + gps + wifi signals etc

outer oyster
#

but anyway from what you've described, there's no good reason to encrypt the payload. Signing the message is enough, and you need to do that anyway

icy cosmos
#

to provide a relatively strong identity level

#

the reason im encrypting so much is because its passing biometrics, passport data, gps, etc.

#

quite routinely

outer oyster
#

it saves you from the nasty key distribution problem of: what if the company needs to revoke its public key โ˜ ๏ธ

icy cosmos
#

lmao

#

so your saying its completely unneeded?

#

to use that layer

outer oyster
#

yeah, I think so. I can't promise I fully understand, though I think I do.

icy cosmos
#

what about on the android client where we arent signing with the attest thing but only the public key

outer oyster
#

encryption is only useful to prevent spying

icy cosmos
#

(by public key i mean the one the device generates)

#

we use play integrity too

#

but its different because im using the google API on the server

outer oyster
icy cosmos
#

its just encrypted by the device's private key, and then its unencrypted by the servers public key (from registration of that device)

#

and it is also encrypted by the company public key

#

which your saying i should drop

#

and all that also has the integrity token in it

#

and then from that point onwards we BLINDLY trust the private key

#

signing

#

on the client

#

we dont require integrity again

outer oyster
#

anyone on android could extract the company public key and use it to encrypt messages no problem

icy cosmos
#

okay so basicaly your saying drop the public key from the company

#

its unneeded and a security problem

#

when we have to re-issue

outer oyster
#

yeah its not necessary and it makes it more complex to both process messages and deal with key distribution. Maybe feed this all into chatgpt or something to validate

icy cosmos
#

ive spoken to chatgpt more than my family the past few days

#

and it contradicts itself

#

EVERY TIME

#

with different advice

#

so i honestly dont trust it

#

thats why i find myself here

#

okay but aside from dropping the public key for the company, what about the fact we still have this issue with the signature being broken or something

#

on the apple side

outer oyster
#

I am not a swift expert, I am having changes verified to make sure I am not insane, but I think it can work with the android handler

icy cosmos
#

tysm man

#

your lowk doing a better job than any AI ive spoken to

#

of like summarising

#

so when we receive a req in appRequest.ts middleware... the nonce isnt needed anymore?

const { encryptedPayload, nonce, signature, deviceUDID } = req.body as AppRequestBody;

#

as the nonce is only for the decryption of the encryptedPayload

outer oyster
#

no you need the nonce for the signature

#
  getPayloadHash: (encryptedPayload: string, nonce: string): Buffer => {
    pidLogger.log('[AndroidHandler] Using string concatenation hashing.');
    const combinedData = `${encryptedPayload}:${nonce}`;
    return crypto.createHash(SIGNATURE_ALGORITHM).update(combinedData, 'utf-8').digest();
  },

if you don't have the nonce you can't properly hash the payload for signature verification

#

the nonce exists to prevent replay attacks

icy cosmos
#

so im kinda struggling, which bit do i need to remove to remove the company public encryption

#

because thats handled here:

import { Response, NextFunction } from 'express';
import { AppRequest, AppRequestBody } from '../types/requests';
import { pidLogger } from '../utils/logger';
import crypto from 'crypto';
import { ENV } from '../config/env';
import { IncomingHttpHeaders } from 'http';
import { derToRawSignature, extractEcdsaSignatureFromAssertion, formatBase64ForPem, rawEcdsaSigToDer } from '../utils/crypto';
import { DeviceManager } from '../database/client';

// 1. IMPORT REQUIRED DEVICE MODEL TYPES
import { DeviceBase } from '../types/device';
import { DeviceLocationsModel } from '../database/models/devices/locations';

// Security Constant
const ENCRYPTION_KEY = Buffer.from(ENV.APP_ENCRYPTION_KEY, 'hex');
const SIGNATURE_ALGORITHM = 'sha256';

// --- SHARED CRYPTOGRAPHIC UTILITIES ---

function decryptPayload(encrypted: string, nonce: string): AppRequestBody | null {
  try {
    const raw = Buffer.from(encrypted, 'base64');
    const IV_LENGTH = 12;
    const TAG_LENGTH = 16;
    if (raw.length < 29) {
      pidLogger.error('[DecryptPayload] Payload too short for GCM structure.');
      return null;
    }

    const iv = raw.slice(0, IV_LENGTH);
    const tag = raw.slice(raw.length - TAG_LENGTH);
    const ciphertext = raw.slice(IV_LENGTH, raw.length - TAG_LENGTH);

    const decipher = crypto.createDecipheriv('aes-256-gcm', ENCRYPTION_KEY, iv);
    decipher.setAuthTag(tag);
    // NOTE: Client (Swift) does NOT use AAD, so we should NOT set it here.
    // However, your provided code sets AAD to the nonce string. I will maintain this, 
    // assuming it is required for your specific configuration.
    decipher.setAAD(Buffer.from(nonce, 'utf-8'));

    const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
    return JSON.parse(decrypted.toString('utf-8')) as AppRequestBody;
  } catch (error) {
    pidLogger.error('[DecryptPayload] Decryption or Tag verification failed', error);
    return null;
  }
}```
outer oyster
#

I wouldn't touch android at this point

icy cosmos
#

and you can see the ENCRYPTION_KEY is the company key, and its used in the decryptPayload function

icy cosmos
outer oyster
#

lets get ios working first

icy cosmos
#

ok lmao

#

couldnt agree more

#

tysm man

outer oyster
#

okay so swift is impenetrable

icy cosmos
#

im a backend dev, but by no means familiar with apple and android

#

lmao

outer oyster
#

most of my issue is I dunno what kind of base64 swift uses internally

icy cosmos
#

okay ill try help u understand that

#

i think i know how

outer oyster
#
let stringToHash = "\(encryptedPayloadB64):\(nonceB64)"
let hashValue = SHA256.hash(data: Data(stringToHash.utf8))
let dataToSign = hashValue.rawData
icy cosmos
#

this is my crypto manager

#

on the apple client

#
struct CryptoManager {
  // Encrypts payload data using AES-256-GCM.
  static func encrypt(jsonObject: JSONObject, key: Data) throws -> (encryptedPayloadB64: String, nonceB64: String) {
   
    let plaintextData = try JSONSerialization.data(withJSONObject: jsonObject, options: [])
   
    guard key.count == 32 else { throw Server.ServerError.networkError(statusCode: 500, body: "Invalid Encryption Key Size") }
    let symmetricKey = SymmetricKey(data: key)
   
    let nonce = AES.GCM.Nonce() // 12 bytes
   
    // Encrypt and authenticate the payload (no AAD used in the client here,
    // as the backend uses the nonce/IV Base64 string as AAD)
    let sealedBox = try AES.GCM.seal(plaintextData, using: symmetricKey, nonce: nonce)
   
    // Full payload = IV (Nonce) || Ciphertext || AuthTag
    var fullPayload = Data()
    fullPayload.append(sealedBox.nonce.withUnsafeBytes { Data($0) }) // 12 bytes (IV/Nonce)
    fullPayload.append(sealedBox.ciphertext)
    fullPayload.append(sealedBox.tag) // 16 bytes (Auth Tag)

    let nonceData = nonce.withUnsafeBytes { Data($0) }
   
    // Generate STANDARD Base64 strings first
    let standardPayloadB64 = fullPayload.base64EncodedString(options: [])
    let standardNonceB64 = nonceData.base64EncodedString(options: [])
   
    // === CRITICAL FIX: Convert to URL-SAFE format for transmission ===
    return (
      encryptedPayloadB64: toBase64URL(standardBase64: standardPayloadB64),
      nonceB64: toBase64URL(standardBase64: standardNonceB64)
    )
  }
}
#

and there you can see at the end

#

ive been tampering

#

between base64 and URL safe

outer oyster
#

why url safe aren't you passing it via post in the body, not url params?

icy cosmos
#

yes its going in the body but gemini got mad at me and said it was a good idea.... lmao

#

and so then i tried iwth and without

#

and ive just been perma confused

#

either way it doesnt seem to work

outer oyster
#

stupid gemini. Body is safe for any data even binary raw

icy cosmos
#

chatgpt looses context from previous messages, and gemini just hallucinates

#

its so annoying lmao

#

and articles online dont have context

#

and i cant glue them together very well

#

so basically

#

the client does this:

icy cosmos
#

downlaoding it

outer oyster
#

this should match android formatting... though maybe its got the wrong flavor of base64

#

which version does your android send as? if its url-safe base64 then it seems like this should work directly with the android handler

#

since it formats the message the same way for hashing

icy cosmos
#

im not sure lemme check

#

ive got android studio on windows

#

lemme check rq

outer oyster
icy cosmos
#

okay yes

#

your right

#

so basically

outer oyster
#

I tried to do the same thing in swift so that you don't need device specific hashing algorithms

icy cosmos
#

we need to mtch that in swift

outer oyster
#

yeah and then you can just use that one handler... if I understood correctly

icy cosmos
#

so ill install that thing you just wrote in mac

#

okay i think so i agree yes

#

okay

#

but aspects are still slightly different because of attest no?

outer oyster
#

where is the attest? isn't that what this signature is?

#

let signatureB64 = try await deviceIdManager.sign(dataToSign: dataToSign) this is the thing you validate on the server side right?

icy cosmos
#

okay hold on okay

#

ive added that new swift

#

onto the client

#

erm

icy cosmos
icy cosmos
#

i think

#

okay so basically looking at the server

#

we want to like

#

simplify the apple to match the android

#

kind of

outer oyster
#

yeah. So I looked at your android code its not generating url-safe base64

icy cosmos
#

yeah its just stock base64

#

as i suspected

outer oyster
#

so iOS needs to not use url-safe

icy cosmos
#

its because gemini told me to "try " url safe on apple

outer oyster
#

or the server wont be able to validate it properly

icy cosmos
#

and your swift u wrote doesnt do URLSAFE

#

i think

#

lemme read rq

#

okay so

#

we've got non url safe

#

coming from the clients now

#

lemme test the server to see what sort of issues its showign

#

okay android is passing

#

happily

#

without issues still

#

apple still complaining

#

but thats fine

#

i think

#

because the server is anticipating URLSAFE

#

base64

#

for apple

outer oyster
#

make sure the server isn't transforming the base64 from apple

outer oyster
icy cosmos
#

maybe lemme vet that good point

#

so that the apple doesnt expect base64

outer oyster
#

you should just use the android handler, unless I missed something?

#

use it for both?

icy cosmos
#

yes your right actually wow yes

#

okay yes

icy cosmos
#

[2025-12-10T19:39:59.916Z] [PID-1] [server.js] REQ 77.103.192.136 | POST | /api/v1/attest/challenge | 200 | 0.694 ms | UA: Verefa-App-IOS
[2025-12-10T19:40:02.024Z] [PID-1] [server.js] REQ 77.103.192.136 | POST | /api/v1/regcomms/devicekeyregister | 201 | 31.228 ms | UA: Verefa-App-IOS
[2025-12-10T19:40:02.287Z] [PID-1] [appRequest.js] LOG [VERIFY DEBUG] Platform: iOS | Public Key (Body Start): MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1IkGdjcaavEJMIBmcga1yBQvaclOUYj8A6H07GgRLn9djSpJzW9y9o48vYrpfYPs3EVUYQ84SMAl5+ULMBAj3Q== | UDID: 75867278-d53c-4efe-8e96-2692580783b8
[2025-12-10T19:40:02.287Z] [PID-1] [appRequest.js] LOG Using string concatenation hashing.
[2025-12-10T19:40:02.287Z] [PID-1] [appRequest.js] LOG Processing signature length: 141
[2025-12-10T19:40:02.287Z] [PID-1] [appRequest.js] ERROR Signature processing failed for UDID: 75867278-d53c-4efe-8e96-2692580783b8. Platform: iOS
[2025-12-10T19:40:02.288Z] [PID-1] [server.js] REQ 77.103.192.136 | POST | /api/v1/device/alive | 400 | 1.140 ms | UA: Verefa-App-IOS

#

still not right im so confused,

#

ive converted it to NON URL SAFE

#

on the client

#
    const rawSignatureBuffer = Buffer.from(signature, 'base64');
    const derSignatureBuffer = handler.processSignature(rawSignatureBuffer);

    if (!derSignatureBuffer) {
      pidLogger.error(`Signature processing failed for UDID: ${deviceUDID}. Platform: ${isAndroid ? 'Android' : 'iOS'}`);
      return res.status(400).json({
        error: 'Signature format invalid or processing failed.',
        errorCode: 'SIGNATURE_FORMAT_INVALID'
      });
    }
#

thats the server block thats tripping

#

im thinking something else is wrong or smth

outer oyster
#

can you hard code the ios client to send the public key and encrypted message from android to check and make sure there's nothing else?

icy cosmos
#

how would i do that because the client doesnt send a public key, it sends a signature

outer oyster
#

like just log what android sends, then copy that into the ios client just for testing

icy cosmos
#

const { encryptedPayload, nonce, signature, deviceUDID } = req.body as AppRequestBody;

outer oyster
#

oh I see hmm

icy cosmos
#

thats what comes

#

ill log the req.body

#

for both

#

and see what we get

#
[2025-12-10T19:48:52.944Z] [PID-1] [appRequest.js] LOG {
  deviceUDID: '136a9319-4519-47d0-bb39-25eb39a60387',
  encryptedPayload: 'WiCDhGtVX5LI5t+hR+3HpQrkNL4hNXH7Qz5hJ6Zc/5e+URMRG6SkGwXBE0qsUYU6M4z7Z64X30X3KfvQvPFI/Y4+YGVix3/bTfXo1HnqmgAenkyEJucxO2kLr1D7gkXtRwi9/rwpD0SRe37J7CRKoZEmiK92vCYk8LWIZaP66BDJyZXJm7FQgMJ/b//YDd7+e1S9uo+wSq/HBRfSPk6emaJY01yOLGrKaC/tXL3NNsk8cKlClND6NwTfCl3ShvQcROgL/TeWhnsqAoPmkNmvofOmnuripzVnuOxR0LLeF1IZLNSf+WecekT3BqeJJEreisSdpw+rXsb5CKhuG5QYuQ==',
  signature: 'MEUCIQD1NFvJOo/hi6qNZrWDHObJgwnYFK9zA7EXpqN+JUgkYQIgIGcY+JQOVLe3NvT/o1ClX4LtJPV4kVyq+v33ED+AK7s=',
  nonce: 'OgI1HovLr1taoe482DJyRw=='
}
[2025-12-10T19:48:52.945Z] [PID-1] [appRequest.js] LOG [VERIFY DEBUG] Platform: Android | UDID: 136a9319-4519-47d0-bb39-25eb39a60387
[2025-12-10T19:48:52.946Z] [PID-1] [appRequest.js] LOG [VERIFY] Platform: Android | Hash: 89f5b1de6a688e237bffdc062b4f4819ddf301b3d010105982f389ce74a4c4e6 | Result: true
[2025-12-10T19:48:52.953Z] [PID-1] [server.js] REQ 77.103.192.136 | POST | /api/v1/device/alive | 200 | 8.666 ms | UA: UnknownBrowser on Android
#

okay so thats an android hit

#

(ive simplified the logging quite a bit)

#

and thats the req.body

#

and here's an apple hit:

[2025-12-10T19:50:43.630Z] [PID-1] [appRequest.js] LOG {
  deviceUDID: '164ca5e6-78bf-48bc-a65a-214332b68a98',
  encryptedPayload: 'isroEkgdjMkk9Xhg5oaFYErJPwMS4eKcLYJfLa9Ya9KNDvMkaa3bpIO71NQ8OeKAsofSug3aZ2qNTp5og5RPVUijcovfR4wyYaHvoJw9TpZRSvGFUR6cRASH5386Pt9LDVGyqZ/HJVGZhoJU4S6RVEBwC2TCD4u3+LO/VNW7ACIm0U3r2htbvTaS7pPGaaui2T3qTO8dTW0anr9/nzgcqyzoVNfQ7EO+qxil8fSoU9Bl0uIAZMsGar6Frbs0vSPEUge1CoO31Jpn8UqaYu4/jMBOfBs+DwD7JBgI02VvbFu2QrM1ZxQCxpwArsGgBIz0Op9zDpVbBjj5icgwv0/xy2SbTOJ2vC6rnn0bueZyd60o0aA8tc+gtBo1j4rjPZicdllkIQDtj1evQC92k7Jz+Lwdj+Qh3sUKPPy+0AlXo+VTNfqwjUwu2AJplTZJZF/lCrEa/FlJAJuIu73JJjib/TAY/W1SCVW0tYT1GzPkaC/jbGOhJA==',
  nonce: 'isroEkgdjMkk9Xhg',
  signature: 'omlzaWduYXR1cmVYRjBEAiA6LpK55LIfvTbSZ+rSbckOUdOA/owXvtQZSFwFZSIM+gIgE1ndsq62t2RWLAEnSEbloGw5r9/T2eN5Cz2NftWloMtxYXV0aGVudGljYXRvckRhdGFYJWG1rH4xtzJCrO145yn7wLII/w2c7PSOoexs7EhmSoiCQAAAAAE='
}
[2025-12-10T19:50:43.631Z] [PID-1] [appRequest.js] LOG [VERIFY DEBUG] Platform: iOS | UDID: 164ca5e6-78bf-48bc-a65a-214332b68a98
[2025-12-10T19:50:43.631Z] [PID-1] [appRequest.js] ERROR Signature processing failed for UDID: 164ca5e6-78bf-48bc-a65a-214332b68a98. Platform: iOS
[2025-12-10T19:50:43.632Z] [PID-1] [server.js] REQ 77.103.192.136 | POST | /api/v1/device/alive | 400 | 1.107 ms | UA: Verefa-App-IOS 
#

is it because the nonce and signature are in a different order?

outer oyster
#

you mean in the log message or are they in the wrong order for hashing?

icy cosmos
#

in the log message

outer oyster
#

in the log message the order is likely random

icy cosmos
#

yea i thought so too but could it be something to do with it?

#

idk

outer oyster
#

it doesn't matter

icy cosmos
#

mk

#

cuz its getting them via keys of the req.body anyway

#

so it shouldnt matter

#

as u say

#

the nonce is significantly shorter in the apple one

#

is that something to worry about?

outer oyster
#

I noticed that, it shouldn't affect anything, but I'm not fully sure

icy cosmos
#

i dont even know what to do anymore

#

i cnat understand

#

why its not working

#

its so annoying

#

gemini doesnt know either

#

"Your Android hit verifies because its signature is valid. Your iOS hit fails because the base64-encoded signature blob is not an ASN.1 ECDSA signature at all. It is structurally different, much longer, and almost certainly coming from a different signing mechanism."

#

"A. The signature starts with โ€œomlzaWduYXR1cmVYโ€ฆโ€

Decoding the first bytes:

omlzaWduYXR1cmVY โ†’ Base64 decodes to:

.signatureXF0

This is not the header of an ECDSA DER signature.
It looks like Apple Secure Enclave envelope metadata, not a raw ECDSA signature."

#

i dont understand how that can be the case

#

because ive made the flow the same as android...

outer oyster
#

oh thats odd, whats the code look like for sign?

#

deviceIdManager.sign(dataToSign: dataToSign)
this function I mean

#

what does it call internally, or is that system-level code?

icy cosmos
#

Iโ€™m comparing the apple and android sign functions and other bade64 bits now

outer oyster
#

yeah now I understand a lot better

#

its probably why your original code didn't work either

#

its a whole attestation object, which is what you said originally, not just a signature

icy cosmos
#

Yeah itโ€™s like cuz I was told I needed to send that for apple

#

But now we donโ€™t want to right?

#

ย  ย ย func sign(dataToSign: Data) async throws -> String {
ย ย  ย  ย  ย 
ย  ย  ย  ย  // Use generateAssertion to get the signature
ย  ย  ย  ย  let keyId = credentials.publicKeyIdentifier
ย  ย  ย  ย  guard !keyId.isEmpty else { throw KeyManagerError.signingFailed }
ย ย  ย  ย  ย 
ย  ย  ย  ย  // The generateAssertion method returns the **Assertion Object**, which is a signed
ย  ย  ย  ย  // PKCS#7 message containing the ECDSA signature. This is what must be sent.
ย  ย  ย  ย  let assertionData = try await generateAssertion(keyId: keyId, clientDataHash: dataToSign)
ย ย  ย  ย  ย 
ย  ย  ย  ย  // The server is expecting this full object, which corresponds to the 141 bytes length,
ย  ย  ย  ย  // and must have verification logic to extract the raw signature from it.
ย  ย  ย  ย  return assertionData.base64EncodedString()
ย  ย  }
ย ย  ย 
ย  ย  private func generateAssertion(keyId: String, clientDataHash: Data) async throws -> Data {
ย  ย  ย  ย  try await withCheckedThrowingContinuation { continuation in
ย  ย  ย  ย  ย  ย  DCAppAttestService.shared.generateAssertion(keyId, clientDataHash: clientDataHash) { assertionData, error in
ย  ย  ย  ย  ย  ย  ย  ย  if let error = error {
ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  continuation.resume(throwing: error)
ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  return
ย  ย  ย  ย  ย  ย  ย  ย  }
ย  ย  ย  ย  ย  ย  ย  ย  guard let assertion = assertionData else {
ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  continuation.resume(throwing: KeyManagerError.signingFailed)
ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  return
ย  ย  ย  ย  ย  ย  ย  ย  }
ย  ย  ย  ย  ย  ย  ย  ย  continuation.resume(returning: assertion)
ย  ย  ย  ย  ย  ย  }
ย  ย  ย  ย  }
ย  ย  }

thats how apple signs

#
    /**
     * Performs a cryptographic signing operation.
     */
    fun signData(context: Context, data: ByteArray): ByteArray {
        val alias = SecureStorage.load(context, DEVICE_PUBLIC_KEY_ALIAS_KEY)
            ?: throw IllegalStateException("Key alias not found. Cannot sign data.")

        val entry = keyStore.getEntry(alias, null) as? KeyStore.PrivateKeyEntry
            ?: throw IllegalStateException("Private Key entry not available for signing.")

        val signature = Signature.getInstance(SIGNING_ALGORITHM)
        signature.initSign(entry.privateKey)
        signature.update(data)

        val result = signature.sign()
        Log.i(TAG, "Signing successful. Output size: ${result.size} bytes.")
        return result
    }

#

and thats how android signs

outer oyster
#

so hence the frustrating difference between clients

icy cosmos
#

im not sure what to do

#

should i change the apple signing

#

to fit with android

#

or should i change the server

#

to support the apple signing

#

in a seperate handler

outer oyster
#

I'm not exactly sure what the "attestation object" is providing

#

but it doesn't look like you're using it currently on the server side?

icy cosmos
#

just ongoing attestation that the client is an apple client and blah blah

outer oyster
#

you just extract the signature

icy cosmos
#

so switch toandroid

outer oyster
#

extractEcdsaSignatureFromAssertion maybe this is broken, but ugh

#

issue android company phones /s

icy cosmos
#

ill try using extractecda

#

on the apple only sigs

#

to reasign them

#

to the sigs

outer oyster
#

what's the implementation of extractEcdsaSignatureFromAssertion look like?

outer oyster
icy cosmos
#

this is all complex logic that gemini was like splurging out

#

a few days ago

#

because it didnt know what else to suggest

#
  let verified: boolean = false;
  // --- 2. Extract and Validate Required Fields ---
  var { encryptedPayload, nonce, signature, deviceUDID } = req.body as AppRequestBody;
  pidLogger(req.body);

  if (!encryptedPayload || !nonce || !signature || !deviceUDID) {
    pidLogger.warn('Missing required fields in secure request body. Likely non-app client attempt.');
    return res.status(400).json({ error: 'Missing required request fields' });
  }

  if (isIos){
    signature = extractEcdsaSignatureFromAssertion(signature);
  }

#

doing this in an attempt

#

to see if i can get the sig specifically for the ios one

#

but theres issues with the function

#
src/middleware/appRequest.ts(119,5): error TS2322: Type 'Buffer<ArrayBufferLike> | null' is not assignable to type 'string | undefined'.
  Type 'null' is not assignable to type 'string | undefined'.
src/middleware/appRequest.ts(119,52): error TS2345: Argument of type 'string' is not assignable to parameter of type 'Buffer<ArrayBufferLike>'.
src/middleware/appRequest.ts(154,44): error TS2769: No overload matches this call.
  Overload 1 of 4, '(arrayBuffer: WithImplicitCoercion<ArrayBufferLike>, byteOffset?: number | undefined, length?: number | undefined): Buffer<ArrayBufferLike>', gave the following error.
    Argument of type 'string | undefined' is not assignable to parameter of type 'WithImplicitCoercion<ArrayBufferLike>'.
      Type 'undefined' is not assignable to type 'WithImplicitCoercion<ArrayBufferLike>'.
  Overload 2 of 4, '(string: WithImplicitCoercion<string>, encoding?: BufferEncoding | undefined): Buffer<ArrayBuffer>', gave the following error.
    Argument of type 'string | undefined' is not assignable to parameter of type 'WithImplicitCoercion<string>'.
      Type 'undefined' is not assignable to type 'WithImplicitCoercion<string>'.
#

which is probably why gemini told me to stop using it

#

im generating a new function with gemini currently

#

and trying to build it to work with the attestation object

outer oyster
#

this is from my own LLM, it is beyond me otherwise

#

but this doesn't try to built its own cbor parser

icy cosmos
#

ty man

#

ill give it a try

#

it needs to return a string i think, not a buffer. because the sig isnt a buffer its just a base64 string

src/middleware/appRequest.ts(119,5): error TS2322: Type 'Buffer<ArrayBufferLike> | null' is not assignable to type 'string | undefined'.
Type 'null' is not assignable to type 'string | undefined'.
src/middleware/appRequest.ts(119,52): error TS2345: Argument of type 'string' is not assignable to parameter of type 'Buffer<ArrayBufferLike>'.
src/middleware/appRequest.ts(154,44): error TS2769: No overload matches this call.
Overload 1 of 4, '(arrayBuffer: WithImplicitCoercion<ArrayBufferLike>, byteOffset?: number | undefined, length?: number | undefined): Buffer<ArrayBufferLike>', gave the following error.
Argument of type 'string | undefined' is not assignable to parameter of type 'WithImplicitCoercion<ArrayBufferLike>'.
Type 'undefined' is not assignable to type 'WithImplicitCoercion<ArrayBufferLike>'.
Overload 2 of 4, '(string: WithImplicitCoercion<string>, encoding?: BufferEncoding | undefined): Buffer<ArrayBuffer>', gave the following error.
Argument of type 'string | undefined' is not assignable to parameter of type 'WithImplicitCoercion<string>'.
Type 'undefined' is not assignable to type 'WithImplicitCoercion<string>'.

#

the same issue i was having on mine

#

i cant seem to work out HOW on earth we are supposed to get the signature out

#

of the assertion object

#

and typically the "apple says" link in that stackoverflow, is gone

#

its this i think

#

i just dont understand why my system isnt able to decode the assertion

#

ive found how this guy gets the sig

#
const assertionObj = await cbor.decodeFirst(assertion);
const { signature, authenticatorData } = assertionObj;
#

with this

#

so if i do the same with that simple cbor function

#

i should be good

#

not working

#

the signature is coming back as undefined...

#

i hate my life

#

oh my god

#

maybe i change apple

#

so that it fits with android

#

and we send the signature like android

#

instead of an attestation object

#

hm

#

yes bcuz if we are trusting a sig from android

#

we might aswell trust apple

#

yes

#

thats a better approach

#

unified middleware

#

none of these weird attestation objects from apple

#

that i hate

outer oyster
#

cbor is like json its just a serialization format

outer oyster
#

after that you can use normal signing, but initial onboarding takes attest

icy cosmos
#

just like cbor(assertion)?

icy cosmos
#

not attestation

#

because the attestation signup part works fine

#

its just the assertions which dont

outer oyster
#

You only need the assertion object for first onboarding

icy cosmos
#

wait whaaa

#

no stress

#

but i dont understand that icl

#

what ive tried since talking tho is basically i tried switching the client to just sending the sig

#

turns out thats not possible

#

because you cant sign with the private key on the client

#

apple doenst let you

#

so the signature cant be generated on the client

#

instead only an assertion object

#

so now im trying to decrypt the assertion object on the server

icy cosmos
#

its still not working

#

im actually starting to wanna die

#

in a few hours this will be my fith day of trying

#

[2025-12-10T23:59:55.929Z] [PID-1] [crypto.js] INFO --- iOS Assertion Object Tracing START ---
[2025-12-10T23:59:55.929Z] [PID-1] [crypto.js] INFO [TRACE-1] Input Buffer Length (Base64 Decoded): 141
[2025-12-10T23:59:55.929Z] [PID-1] [crypto.js] INFO [TRACE-3] CBOR Decoding Successful.
[2025-12-10T23:59:55.929Z] [PID-1] [crypto.js] INFO [TRACE-7] Signature (DER=71) and AuthData (37) extracted successfully.
[2025-12-10T23:59:55.929Z] [PID-1] [crypto.js] INFO --- iOS Assertion Object Tracing END (Success) ---
[2025-12-10T23:59:55.930Z] [PID-1] [appRequest.js] LOG [VERIFY DEBUG] Platform: iOS | ClientDataHash: ef58119ab4c72c988d5a0830de2eb90b9a3c4359bfb8e0bdb2a91e59f5b284fe | Final Hash Used: b7793f0b1fcb4c50689de3a6f92754f3c658df5233002672bea837650154d388
[2025-12-10T23:59:55.930Z] [PID-1] [appRequest.js] LOG [VERIFY] Platform: iOS | Hash: b7793f0b1fcb4c50689de3a6f92754f3c658df5233002672bea837650154d388 | Result: true
[2025-12-10T23:59:55.930Z] [PID-1] [appRequest.js] ERROR [DecryptPayload] Decryption or Tag verification failed Error: Unsupported state or unable to authenticate data
at Decipheriv.final (node:internal/crypto/cipher:193:29)
at decryptPayload (/app/dist/middleware/appRequest.js:33:80)
at appRequestHandler (/app/dist/middleware/appRequest.js:180:34)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

#

finally

#

its working

#

kind of

#

lmao

#

its just started working

outer oyster
icy cosmos
#

finallly

outer oyster
#

yay

icy cosmos
#

now the error is beyond the verification

outer oyster
#

congrats

icy cosmos
#

i literalyl just marched aorund my room im so happy dude

#

lmao

#

actually goated

#

taken so long

#

now thers like anothe error

outer oyster
#

yeah nasty problems like this where you can't properly debug are so frustrating

icy cosmos
#

but its beyond the verification

#

and thats all that matters

icy cosmos
#

and the sort of enclosed system

#

whereby you cant see the client stuff

#

when it sends it

outer oyster
#

well I suppose it could just be misverifying and passing everything

icy cosmos
#

lmao

#

we wont talk about that

#

ill change some stuff server side to vet against that somehow

#

maybe

#

hmm

#

thats a good point actually

outer oyster
#

yeah just swap a character in the payload and see if it verifies

icy cosmos
#

or just in the sig

#

as it goes into crypto.verify

outer oyster
#

the sig has structure iirc

#

its made out of two numbers or something

#

oh nvm you're right

#

I was confusing with key

icy cosmos
#

yeah its finally just the 1 sig thing

#

ill share the final code here

#

so u can at least see how the crypto work

#

incase u need it one day

#

lmao

#

thank you so much man

#

now ive got to like fix the next issue its causing

#

but at least its verifying

#

thats the crypto that takes the attestation object.

outer oyster
icy cosmos
#

nah u actually helped me understand my own architechture

#

lmao

#

i didnt even know it very well prior

#

or comprehend it that well

#

its mostly tutorials and llm's

#

but now i actually comprehend the flow better

#

cuz like im tryna build an industry standard or better security system, and im not an industry standard guy

#

im like 19

#

lmao

#

i hate vibe coding too icl

#

but working with apple has forced me to

#

cuz i had no experience with DCAppAttest or much complex swift

#

only kotlin

outer oyster
icy cosmos
#

its like super vague

#

like theres no real practical demos

#

showing how to do it

#

its just instructions for clever dudes

#

and somehow i cant find many typescript instances of this being done before

#

maybe its treated as an industry secret as to the most efficient comms path

#

i guess its also quite sensitive data

#

for a company to be disclosing how they use attest

#

it would be nice kinda if apple hosted an attest endpoint

#

like play integrity

#

to save all this effort

#

except i do fear my apps deployment with play integrity will get bottlenecked by the integrity rate limits if i get loads of users

#

but there is a google form u fill in to request more usage

#

for free i think

#

so thats promising

#

at least apple attest is technically unrestricted usage as u do the verification urself

#

play integrity still offers that tho too

#

so apple has no excuses really

icy cosmos
#

evidently not even nearly out of the woods

#

the decryption isnt passing

#

on apple

#

lmao

#

and wont

outer oyster
icy cosmos
#

yeah thats one thing ive got in the back of my mind

#

its something that i dont think im gonna need

#

i think the nonce is in UTF8 on android and BASE64 on apple

#

which is causing issues

#

when it comes to decrypting the 2 with the same func

outer oyster
#

the rule of thumb, as I understand it is: signing is for anti-tampering, and encryption is for anti-eavesdropping

icy cosmos
#

surely tho i need to encrypt then

#

because the app sends data like passport numbers

#

and if that was breached via an admin on a school network where someone was using the app

#

then that would uh

#

cause me to get put in jail

outer oyster
#

it doesn't hurt anything to have it. But the extra layer of encryption doesn't help that much https is already industry state of the art

#

and either way your server has the capability to decrypt it

icy cosmos
#

but cant a local network https be violated by the network admins

outer oyster
#

but mitm always starts with the user intentionally installing a certificate authority

#

and if the user can be convinced to do that, they can be convinced into doing any number of other privilege escalation tricks to introspect your app

#

but like I said: it doesn't hurt to have another layer of encryption, if you need peace of mind. You need to figure out how you're going to revoke the keys though, if worst case happens.

#

with https there's already key revocation infrastructure

icy cosmos
#

okay

#

i see

#

well i can just revoke the keys server side, and then encryption will reject

#

and the apps will all go dead in the water basically

#

and they will all have to update their clients

outer oyster
#

not quite, if the attacker has violated the https tunnel, then clients will still be sending messages to you encrypted with the corporate public key

icy cosmos
#

i see and that key couldve been leaked

#

from my server

outer oyster
#

so all that data will leak to an attacker, because they can decrypt it

#

yeah that's the worst case scenario

icy cosmos
#

what if like the clients ask the server "yo has the key leaked" lmao

outer oyster
#

it has the same failure mode as https cert private key leaking

icy cosmos
#

first

outer oyster
icy cosmos
#

oh yea

#

yea your right actually

outer oyster
#

'cause the cert private key is also on the server

icy cosmos
#

so its basically an added security risk if my servers get breached

outer oyster
#

no no extra risk

icy cosmos
#

but its a nice end 2 end convenience until that happens

outer oyster
#

it does provide a little extra defense, though I feel its more "security through obscurity" kind of bonus

icy cosmos
#

until my company gets breached

outer oyster
#

but is it worth it for the additional client-server complexity

#

that's the tradeoff

icy cosmos
#

yeah probably not

#

given i want fast comms aswell

outer oyster
#

this won't impact performance mesurably

icy cosmos
#

and small overall request objects for low data environments

outer oyster
#

you're not sending that many messages, whether you encrypt or not

icy cosmos
#

we send a message when a device stops moving

#

so for like hundreds of users in transit they would technically be all firing off quite alot

#

but yes i agree your right its not measurable

#

its sort of like

#

extra complexity until we get hacked

outer oyster
#

ah well I guess you might be right in aggregate it probably is measurable

icy cosmos
#

hmmm

#

not sure anymore

#

because its a complete like product collapse if the company key leaks

#

like complete collapse

outer oyster
icy cosmos
#

all clients immediately are breached

outer oyster
icy cosmos
#

we are trying to get users to keep it on

#

and not just approx, but precise

#

and always enabled

#

so that we can do location based, and biometric based, auth

#

in real world environments

outer oyster
#

gotta supply them with company phones

icy cosmos
#

so glance at a camera at a venue and we let you in based on your face, and the pool of devices that are reporting to be near the gate

outer oyster
#

but it is a cool idea

icy cosmos
#

lmao

#

but yea

#

its gonna be hard no doubt

#

to get continuous valid data from the clients

#

Verefa bridges the physical and digital world with secure, face-based identity verification. From online logins to event access, Verefa makes authentication seamless, smart, and secure.

#

the product is rlly close

#

but we are just working on this auth flow aspect

#

whilst switching from a very much so dev environment to an encrypted secure flow

outer oyster
icy cosmos
#

tell me about it

#

lmao

#

i was gonna do React native

#

but it was taking me too long to get comfortable

#

so i bought a mac mini

#

and now im like juggling swift, kotlin, the nodejs servers, and the frickin marketing sites and the server that runs that

#

so its a fun experience

#

hence 1:35am

outer oyster
icy cosmos
#

i tried that too

#

lmao

outer oyster
#

(tho flutter does have some neat ideas in it, the cross-platform dev experience is bad)

icy cosmos
#

i went from flutter, to react native, and then i waslike what the hell man these are all so stupid

outer oyster
#

yeah roll your own, its the only thing sane

icy cosmos
#

and for like longevity

#

and actual control

#

becuase im reading passports

#

i need like proper language level stuff

#

not some high level wrapper of the language

#

reading the passport data took so long to get right

outer oyster
#

yeah. Some of the "native" libs for flutter are like "lets serialize every os message through a json protocol, its super slow, this'll be great"

icy cosmos
#

it seems nobody has ever done it in kotlin successfully

#

i cant even understand how there is so little docs or stack overflow stuff about things like nfc scanning

icy cosmos
#

lmao

#

the trust levels too is a problem

#

like do i trust the BAC authentication of a passport that gets sent from the client, or do we copy the passport data to the server and then run BAC authentication on it

#

theres like the security argument that storing passport data server side is a huge risk if we get hacked

outer oyster
icy cosmos
#

lowk

outer oyster
#

the security theater is reaching hollywood levels there

#

apple is way better

icy cosmos
#

yeah i feel that way more and more

#

swift is fast for implementing new features and stuff

#

and kotlin feels like this janky java environment with some new stuff

#

and its not nearly as safe

#

because its an android

#

lmao

outer oyster
icy cosmos
#

but storing passport data locally on the clients and just claiming auth is safer for the companys media image when we get our servers breached

icy cosmos
#

but i use cloud services that im pretty sure dont offer that

outer oyster
#

I think aws offers something? but I haven't really looked into it much, in some ways cloud is a downgrade

#

its probably expensive

icy cosmos
#

and we do biometrics on the passport face vs a scan in real time and i pass that off to a third party GPU provider for speed

#

but that again is a concern with security really

outer oyster
icy cosmos
#

yeah thats the concept

#

except the hard part is trusting the phone

outer oyster
#

I'm not sure what happens when you get audited though

icy cosmos
#

hence this level of like data transmission trust im trying to build

#

to ensure we cant get users tampering the passport data and stuff

icy cosmos
#

lmao

#

were also trying to enforce 1 account per human

outer oyster
#

ah good luck

icy cosmos
#

using intelligent registration signals so like likelihood of someone called "james" registering twice in the same ip area and whatnot

#

and that is a minefield of security too because we dont want people getting into other peoples accounts by claiming they lost their old device

#

when our primary trust data is biometrics, location, and sometimes passports

#

the goal is to throw away passwords

#

and that brings with it a ton of complication

outer oyster
#

I am fond of security where you get mailed a blank steel plate and a set of letter blanks you hammer into the metal to preserve your account recovery key

icy cosmos
#

lmaaoo

#

thats actually valid

outer oyster
#

(steel not paper so if your house burns down you don't get locked out for life)

icy cosmos
#

i feel like im building a product that will have an "acceptable rate of account violations"

#

whereby a percentage of users get violated

#

which is a crazy admission really

outer oyster
#

yeah

icy cosmos
#

and their keychain is wiped

outer oyster
#

or get a new phone

icy cosmos
#

our server has to consider it a new phone when and if they reinstall

outer oyster
#

actually apple might be able to handle migration, but ideally the keys shouldn't be migratable

icy cosmos
#

and deduce using signals like location and their face if they have an account already and then assign it to that phone

icy cosmos
#

instead your account gets repositioned

outer oyster
icy cosmos
#

to a new device public key

icy cosmos
#

lmao

#

thats why we use nfc passports as final truth

#

and whether the device public key is registered with us to an account or not

outer oyster
#

these are like US govt passports or do you mean something different?

icy cosmos
#

yeah the gov ones

#

they have a little NFC chip in them

#

which phones read

#

we get you to scan it

outer oyster
#

oh neat I never realized they had nfc in them. I guess I need to get a little wire mesh bag

icy cosmos
#

nah its okay

#

the hacker needs the details

#

(name, dob, expiry)

#

to read the nfc chip

outer oyster
#

oh cool

icy cosmos
#

its encrypted by those

#

to prevent slideby attacks like that

#

so we get you to take a picture of it first

#

to read those values

outer oyster
#

thats some forward thinking security from the govt. unexpected

icy cosmos
#

not really a picture tho, weve put a camera text detection model on the app

#

which is so janky because its different for ios vs android

icy cosmos
#

its in like most passports now

#

all around the world

#

and somehow all the agencies use the same protocols

#

within reason

#

so most passports can be read like that

#

and authenticated against the ICAO root certificates

outer oyster
#

I wonder if they got china on board, since iirc china doesn't like any of the common western encryption algorithms

icy cosmos
#

lmao i dont think so

#

theres an extensive list

#

but yeah i mean im pretty sure afghanistan isnt on there for sure

#

i cant remember entirely but theres a good handful who arent

#

unfortunately theres 2 versions of the protocol too

#

PACE and BAC

#

and some passports only have the older one

#

and vice versa

#

so its a difficult game

#

ultimately the entire product is a consumer grade surveillance network that you can opt into, and leverage that network to authenticate yourself among the network without needing passwords, usernames and stuff like that

#

and its built for consumer and business customers for IAM software for a business to manage customers + staff

icy cosmos
#

my servers are being heavily hit with botnets the past few days

#

its crazy

icy cosmos
#

its all completely working now

#

ive fixed it entirely

#

the encryption works

#

everything

#

its because on android i was supplying the nonce as the AAD for encryption

#

and on apple i wasnt supplying anything

#

so the encryption was completely different too

#

fixed that now

#

and introduced TONS of ratelimiting on the reverse proxy

#

because im being inundated with a botnet

#

lmao

#

i kept the company public key system

#

so yes we are in a precarious situation if the keys get leaked

#

but its just an added layer of security until that happens

#

and this is arguably an MVP

#

which will have like early users primarily

#

before i hire professional developers

#

lmao

icy cosmos
#

dude i swear i never sleep these days its crazy