Make platform RSA modulusLength configurable on lti.setup()
Summary
Auth.generatePlatformKeyPair hardcodes crypto.generateKeyPairSync('rsa', { modulusLength: 4096, ... }). This call is synchronous (blocking the node event loop), and on small instances (AWS Lambda, small EC2, etc) can take anywhere from a couple of seconds to half a minute on first registration, with significant run-to-run variance even on identical hardware. I'd like to make the modulus length configurable via a new options.keySize on lti.setup(), defaulting to the current 4096 so existing consumers see no behavior change.
Why 4096-bit synchronous keygen is painful
Two things compound:
-
It's synchronous. crypto.generateKeyPairSync blocks the event loop. Nothing else in the process runs while it executes, so during a first-time dynamic registration, the whole tool freezes for the duration of keygen.
-
It has high intrinsic variance. RSA keygen retries until the randomly-sampled candidate primes pass primality testing. A "slow-luck" sample can take several times the median on the same hardware. I ran six consecutive fresh 4096-bit registrations on identical local hardware:
| Run |
keygen + Mongo write |
Handler total |
| 1 |
604 ms |
781 ms |
| 2 |
335 ms |
538 ms |
| 3 |
436 ms |
604 ms |
| 4 |
708 ms |
877 ms |
| 5 |
969 ms |
1138 ms |
| 6 |
876 ms |
1022 ms |
The keygen step spans 335 ms → 969 ms, a ~2.9× spread. The same multiplicative variance on a smaller, slower CPU (think small EC2 or Lambda cold start) drags the tail into the double-digit-seconds range, which is what users notice.
For comparison, the same setup with modulusLength: 2048 produces handler times in the 150-300 ms range, an order-of-magnitude improvement that also flattens the variance pattern, a smaller prime search space means proportionally less tail.
Why 2048 is a safe option to offer
- IMS LTI 1.3 spec:
1EdTech Security Framework v1.0 requires platforms and tools to support RS256 on a minimum key size of 2048 bits. 4096 is allowed, not required. (spec)
- NIST SP 800-57: RSA-2048 is considered secure through at least 2030.
- Industry practice: Canvas, Moodle, and Blackboard's own LTI tools use 2048-bit RSA. Offering 2048 as an option puts ltijs consumers in the mainstream, not below it.
Proposed solution
A single new options.keySize on lti.setup(), defaulting to 4096:
lti.setup(KEY, db, {
// ...existing options
keySize: 2048,
})
Threaded as:
Provider.setup() validates that keySize is an integer ≥ 2048 (throws INVALID_KEYSIZE otherwise), stores it on a private field, default 4096.
Provider.registerPlatform() accepts an optional keySize arg that falls back to the configured value (mirrors the existing getPlatform / ENCRYPTIONKEY / Database fallback pattern, so the option works through both the direct call path and the DynamicRegistration path where this is the service instance, not the Provider).
DynamicRegistration constructor accepts and stores keySize, then passes it through to registerPlatform.
Auth.generatePlatformKeyPair(..., keySize = 4096) uses it for modulusLength.
Default value is unchanged at 4096, so this is purely additive, no consumer who isn't passing the new option sees any behavior change.
PR
PR with the change + tests: #288
Make platform RSA
modulusLengthconfigurable onlti.setup()Summary
Auth.generatePlatformKeyPairhardcodescrypto.generateKeyPairSync('rsa', { modulusLength: 4096, ... }). This call is synchronous (blocking the node event loop), and on small instances (AWS Lambda, small EC2, etc) can take anywhere from a couple of seconds to half a minute on first registration, with significant run-to-run variance even on identical hardware. I'd like to make the modulus length configurable via a newoptions.keySizeonlti.setup(), defaulting to the current4096so existing consumers see no behavior change.Why 4096-bit synchronous keygen is painful
Two things compound:
It's synchronous.
crypto.generateKeyPairSyncblocks the event loop. Nothing else in the process runs while it executes, so during a first-time dynamic registration, the whole tool freezes for the duration of keygen.It has high intrinsic variance. RSA keygen retries until the randomly-sampled candidate primes pass primality testing. A "slow-luck" sample can take several times the median on the same hardware. I ran six consecutive fresh 4096-bit registrations on identical local hardware:
The keygen step spans 335 ms → 969 ms, a ~2.9× spread. The same multiplicative variance on a smaller, slower CPU (think small EC2 or Lambda cold start) drags the tail into the double-digit-seconds range, which is what users notice.
For comparison, the same setup with
modulusLength: 2048produces handler times in the 150-300 ms range, an order-of-magnitude improvement that also flattens the variance pattern, a smaller prime search space means proportionally less tail.Why 2048 is a safe option to offer
1EdTech Security Framework v1.0requires platforms and tools to support RS256 on a minimum key size of 2048 bits. 4096 is allowed, not required. (spec)Proposed solution
A single new
options.keySizeonlti.setup(), defaulting to4096:Threaded as:
Provider.setup()validates thatkeySizeis an integer ≥ 2048 (throwsINVALID_KEYSIZEotherwise), stores it on a private field, default4096.Provider.registerPlatform()accepts an optionalkeySizearg that falls back to the configured value (mirrors the existinggetPlatform/ENCRYPTIONKEY/Databasefallback pattern, so the option works through both the direct call path and theDynamicRegistrationpath wherethisis the service instance, not the Provider).DynamicRegistrationconstructor accepts and storeskeySize, then passes it through toregisterPlatform.Auth.generatePlatformKeyPair(..., keySize = 4096)uses it formodulusLength.Default value is unchanged at 4096, so this is purely additive, no consumer who isn't passing the new option sees any behavior change.
PR
PR with the change + tests: #288