@@ -3,6 +3,16 @@ import crypto from "node:crypto";
33import fs from "node:fs" ;
44import path from "node:path" ;
55import { resolveStateDir } from "../config/paths.js" ;
6+ import {
7+ base64UrlDecode ,
8+ deriveEd25519PublicKeyRaw ,
9+ ed25519PrivateKeyPemFromRaw ,
10+ ed25519PublicKeyPemFromRaw ,
11+ normalizeEd25519PublicKeyBase64Url ,
12+ publicKeyRawBase64UrlFromEd25519Pem ,
13+ signEd25519Payload ,
14+ verifyEd25519Signature ,
15+ } from "./ed25519-signature.js" ;
616import { privateFileStoreSync } from "./private-file-store.js" ;
717
818/** Gateway/device Ed25519 identity used for APNs relay and gateway authentication. */
@@ -31,51 +41,12 @@ function resolveDefaultIdentityPath(): string {
3141 return path . join ( resolveStateDir ( ) , "identity" , "device.json" ) ;
3242}
3343
34- const ED25519_SPKI_PREFIX = Buffer . from ( "302a300506032b6570032100" , "hex" ) ;
35- const ED25519_PKCS8_PRIVATE_PREFIX = Buffer . from ( "302e020100300506032b657004220420" , "hex" ) ;
36-
37- function base64UrlEncode ( buf : Buffer ) : string {
38- return buf . toString ( "base64" ) . replaceAll ( "+" , "-" ) . replaceAll ( "/" , "_" ) . replace ( / = + $ / g, "" ) ;
39- }
40-
41- function base64UrlDecode ( input : string ) : Buffer {
42- const normalized = input . replaceAll ( "-" , "+" ) . replaceAll ( "_" , "/" ) ;
43- const padded = normalized + "=" . repeat ( ( 4 - ( normalized . length % 4 ) ) % 4 ) ;
44- return Buffer . from ( padded , "base64" ) ;
45- }
46-
47- function pemEncode ( label : "PUBLIC KEY" | "PRIVATE KEY" , der : Buffer ) : string {
48- const body =
49- der
50- . toString ( "base64" )
51- . match ( / .{ 1 , 64 } / g)
52- ?. join ( "\n" ) ?? "" ;
53- return `-----BEGIN ${ label } -----\n${ body } \n-----END ${ label } -----\n` ;
54- }
55-
5644// Swift stores raw Ed25519 key bytes; Node crypto needs DER/PEM wrappers around them.
57- function publicKeyPemFromRaw ( publicKeyRaw : Buffer ) : string {
58- return pemEncode ( "PUBLIC KEY" , Buffer . concat ( [ ED25519_SPKI_PREFIX , publicKeyRaw ] ) ) ;
59- }
60-
61- function privateKeyPemFromRaw ( privateKeyRaw : Buffer ) : string {
62- return pemEncode ( "PRIVATE KEY" , Buffer . concat ( [ ED25519_PKCS8_PRIVATE_PREFIX , privateKeyRaw ] ) ) ;
63- }
64-
65- function derivePublicKeyRaw ( publicKeyPem : string ) : Buffer {
66- const key = crypto . createPublicKey ( publicKeyPem ) ;
67- const spki = key . export ( { type : "spki" , format : "der" } ) as Buffer ;
68- if (
69- spki . length === ED25519_SPKI_PREFIX . length + 32 &&
70- spki . subarray ( 0 , ED25519_SPKI_PREFIX . length ) . equals ( ED25519_SPKI_PREFIX )
71- ) {
72- return spki . subarray ( ED25519_SPKI_PREFIX . length ) ;
73- }
74- return spki ;
75- }
45+ const publicKeyPemFromRaw = ed25519PublicKeyPemFromRaw ;
46+ const privateKeyPemFromRaw = ed25519PrivateKeyPemFromRaw ;
7647
7748function fingerprintPublicKey ( publicKeyPem : string ) : string {
78- const raw = derivePublicKeyRaw ( publicKeyPem ) ;
49+ const raw = deriveEd25519PublicKeyRaw ( publicKeyPem ) ;
7950 return crypto . createHash ( "sha256" ) . update ( raw ) . digest ( "hex" ) ;
8051}
8152
@@ -305,32 +276,19 @@ export function loadDeviceIdentityIfPresent(
305276
306277/** Sign a UTF-8 payload with a PEM Ed25519 private key and return base64url bytes. */
307278export function signDevicePayload ( privateKeyPem : string , payload : string ) : string {
308- const key = crypto . createPrivateKey ( privateKeyPem ) ;
309- const sig = crypto . sign ( null , Buffer . from ( payload , "utf8" ) , key ) ;
310- return base64UrlEncode ( sig ) ;
279+ return signEd25519Payload ( privateKeyPem , payload ) ;
311280}
312281
313282/** Normalize PEM or raw base64/base64url public keys to canonical raw base64url bytes. */
314283export function normalizeDevicePublicKeyBase64Url ( publicKey : string ) : string | null {
315- try {
316- if ( publicKey . includes ( "BEGIN" ) ) {
317- return base64UrlEncode ( derivePublicKeyRaw ( publicKey ) ) ;
318- }
319- const raw = base64UrlDecode ( publicKey ) ;
320- if ( raw . length === 0 ) {
321- return null ;
322- }
323- return base64UrlEncode ( raw ) ;
324- } catch {
325- return null ;
326- }
284+ return normalizeEd25519PublicKeyBase64Url ( publicKey ) ;
327285}
328286
329287/** Derive the stable device id from PEM or raw base64/base64url public key material. */
330288export function deriveDeviceIdFromPublicKey ( publicKey : string ) : string | null {
331289 try {
332290 const raw = publicKey . includes ( "BEGIN" )
333- ? derivePublicKeyRaw ( publicKey )
291+ ? deriveEd25519PublicKeyRaw ( publicKey )
334292 : base64UrlDecode ( publicKey ) ;
335293 if ( raw . length === 0 ) {
336294 return null ;
@@ -343,7 +301,7 @@ export function deriveDeviceIdFromPublicKey(publicKey: string): string | null {
343301
344302/** Export a PEM Ed25519 public key as canonical raw base64url bytes. */
345303export function publicKeyRawBase64UrlFromPem ( publicKeyPem : string ) : string {
346- return base64UrlEncode ( derivePublicKeyRaw ( publicKeyPem ) ) ;
304+ return publicKeyRawBase64UrlFromEd25519Pem ( publicKeyPem ) ;
347305}
348306
349307/** Verify a UTF-8 payload signature against PEM or raw base64/base64url public key material. */
@@ -352,23 +310,5 @@ export function verifyDeviceSignature(
352310 payload : string ,
353311 signatureBase64Url : string ,
354312) : boolean {
355- try {
356- const key = publicKey . includes ( "BEGIN" )
357- ? crypto . createPublicKey ( publicKey )
358- : crypto . createPublicKey ( {
359- key : Buffer . concat ( [ ED25519_SPKI_PREFIX , base64UrlDecode ( publicKey ) ] ) ,
360- type : "spki" ,
361- format : "der" ,
362- } ) ;
363- const sig = ( ( ) => {
364- try {
365- return base64UrlDecode ( signatureBase64Url ) ;
366- } catch {
367- return Buffer . from ( signatureBase64Url , "base64" ) ;
368- }
369- } ) ( ) ;
370- return crypto . verify ( null , Buffer . from ( payload , "utf8" ) , key , sig ) ;
371- } catch {
372- return false ;
373- }
313+ return verifyEd25519Signature ( { publicKey, payload, signatureBase64Url } ) ;
374314}
0 commit comments