Skip to content

Commit ed84fee

Browse files
Add RS256 support for NSS v5.x compatibility
NSS v5.x may only support RS256 for external IdP token verification. This adds RS256 key generation alongside ES256, with RS256 as the primary signing algorithm for better compatibility with older servers.
1 parent 9c620d1 commit ed84fee

2 files changed

Lines changed: 58 additions & 9 deletions

File tree

src/idp/keys.js

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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

src/idp/provider.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ export async function createProvider(issuer) {
314314
grant_types: ['authorization_code', 'refresh_token'],
315315
response_types: ['code'],
316316
token_endpoint_auth_method: 'none', // Public clients by default
317-
id_token_signed_response_alg: 'ES256', // ES256 is what we support
317+
id_token_signed_response_alg: 'RS256', // RS256 for NSS compatibility
318318
},
319319

320320
// Response modes

0 commit comments

Comments
 (0)