@@ -21,16 +21,15 @@ function getJwksPath() {
2121}
2222
2323/**
24- * Generate a new EC P-256 key pair for signing
24+ * Generate a new EC P-256 key pair for signing (ES256)
2525 * @returns {Promise<object> } - JWK key pair with private key
2626 */
27- async function generateSigningKey ( ) {
27+ async function generateES256Key ( ) {
2828 const { publicKey, privateKey } = await jose . generateKeyPair ( 'ES256' , {
2929 extractable : true ,
3030 } ) ;
3131
3232 const privateJwk = await jose . exportJWK ( privateKey ) ;
33- const publicJwk = await jose . exportJWK ( publicKey ) ;
3433
3534 // Add metadata
3635 const kid = crypto . randomUUID ( ) ;
@@ -45,6 +44,44 @@ async function generateSigningKey() {
4544 } ;
4645}
4746
47+ /**
48+ * Generate a new RSA key pair for signing (RS256)
49+ * NSS v5.x may only support RS256 for external IdP verification
50+ * @returns {Promise<object> } - JWK key pair with private key
51+ */
52+ async function generateRS256Key ( ) {
53+ const { publicKey, privateKey } = await jose . generateKeyPair ( 'RS256' , {
54+ modulusLength : 2048 ,
55+ extractable : true ,
56+ } ) ;
57+
58+ const privateJwk = await jose . exportJWK ( privateKey ) ;
59+
60+ // Add metadata
61+ const kid = crypto . randomUUID ( ) ;
62+ const now = Math . floor ( Date . now ( ) / 1000 ) ;
63+
64+ return {
65+ ...privateJwk ,
66+ kid,
67+ use : 'sig' ,
68+ alg : 'RS256' ,
69+ iat : now ,
70+ } ;
71+ }
72+
73+ /**
74+ * Generate signing keys (both ES256 and RS256 for compatibility)
75+ * @returns {Promise<object[]> } - Array of JWK key pairs
76+ */
77+ async function generateSigningKeys ( ) {
78+ // Generate RS256 first (primary, for NSS compatibility)
79+ const rs256Key = await generateRS256Key ( ) ;
80+ // Also generate ES256 for modern clients
81+ const es256Key = await generateES256Key ( ) ;
82+ return [ rs256Key , es256Key ] ;
83+ }
84+
4885/**
4986 * Generate cookie signing keys
5087 * @returns {string[] } - Array of random secret strings
@@ -66,25 +103,36 @@ export async function initializeKeys() {
66103 try {
67104 // Try to load existing keys
68105 const data = await fs . readJson ( getJwksPath ( ) ) ;
106+
107+ // Check if we have RS256 key (needed for NSS compatibility)
108+ const hasRS256 = data . jwks . keys . some ( ( k ) => k . alg === 'RS256' ) ;
109+ if ( ! hasRS256 ) {
110+ console . log ( 'Adding RS256 key for NSS compatibility...' ) ;
111+ const rs256Key = await generateRS256Key ( ) ;
112+ data . jwks . keys . unshift ( rs256Key ) ; // RS256 first (primary)
113+ await fs . writeJson ( getJwksPath ( ) , data , { spaces : 2 } ) ;
114+ console . log ( 'RS256 key added.' ) ;
115+ }
116+
69117 return data ;
70118 } catch ( err ) {
71119 if ( err . code !== 'ENOENT' ) throw err ;
72120
73- // Generate new keys
121+ // Generate new keys (both RS256 and ES256)
74122 console . log ( 'Generating new IdP signing keys...' ) ;
75- const signingKey = await generateSigningKey ( ) ;
123+ const signingKeys = await generateSigningKeys ( ) ;
76124 const cookieKeys = generateCookieKeys ( ) ;
77125
78126 const data = {
79127 jwks : {
80- keys : [ signingKey ] ,
128+ keys : signingKeys ,
81129 } ,
82130 cookieKeys,
83131 createdAt : new Date ( ) . toISOString ( ) ,
84132 } ;
85133
86134 await fs . writeJson ( getJwksPath ( ) , data , { spaces : 2 } ) ;
87- console . log ( 'IdP signing keys generated and saved.' ) ;
135+ console . log ( 'IdP signing keys generated and saved (RS256 + ES256) .' ) ;
88136
89137 return data ;
90138 }
@@ -100,7 +148,8 @@ export async function getPublicJwks() {
100148 // Return only public key components
101149 const publicKeys = jwks . keys . map ( ( key ) => {
102150 // For EC keys, remove 'd' (private key component)
103- const { d, ...publicKey } = key ;
151+ // For RSA keys, remove 'd', 'p', 'q', 'dp', 'dq', 'qi' (private components)
152+ const { d, p, q, dp, dq, qi, ...publicKey } = key ;
104153 return publicKey ;
105154 } ) ;
106155
0 commit comments