Description
When enableOnlineChecks is enabled, SignedDataVerifier.checkOCSPStatus parses the matching response's thisupdate and nextupdate fields with parseX509Date. The installed jsrsasign OCSP parser preserves the trailing Z in GeneralizedTime values such as 20260905033451Z.
However, parseX509Date only matches 14 digits with no timezone suffix:
private parseX509Date(date: string) {
return new Date(date.replace(
/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/,
'$4:$5:$6 $2/$3/$1'
));
}
A value ending in Z does not match the expression and reaches new Date unchanged. Node.js returns Invalid Date, whose timestamp is NaN. Both timestamp comparisons in the following condition then evaluate to false:
if (
singleResponse.status.status !== 'good' ||
new Date().getTime() + MAX_SKEW < issueDate.getTime() ||
nextDate.getTime() < new Date().getTime() - MAX_SKEW
) {
throw new VerificationException(VerificationStatus.FAILURE)
}
Consequently, an otherwise accepted OCSP response with status good is not rejected when nextUpdate is expired or thisUpdate is in the future. The fallback parsing of values without Z also uses the process's local timezone instead of UTC.
Reproduction
From the repository root, after building the package, run:
const { KJUR } = require('jsrsasign');
const { SignedDataVerifier, Environment } = require('./dist/index.js');
const verifier = new SignedDataVerifier([], true, Environment.SANDBOX, 'com.example');
// Encode and decode a SingleResponse to demonstrate the dependency's actual output.
const encoded = new KJUR.asn1.ocsp.SingleResponse({
certid: {
alg: 'sha256',
issname: '00'.repeat(32),
isskey: '11'.repeat(32),
sbjsn: '01',
},
status: { status: 'good' },
thisupdate: '20200101000000Z',
nextupdate: '20200102000000Z',
});
const response = new KJUR.asn1.ocsp.OCSPParser().getSingleResponse(encoded.tohex());
// Accessing the TypeScript-private method here is possible in emitted JavaScript.
const issueDate = verifier.parseX509Date(response.thisupdate);
const nextDate = verifier.parseX509Date(response.nextupdate);
const now = Date.now();
const MAX_SKEW = 60000;
console.log(response.thisupdate, response.nextupdate);
console.log(issueDate.getTime(), nextDate.getTime());
console.log(
'Rejected by freshness condition:',
response.status.status !== 'good' ||
now + MAX_SKEW < issueDate.getTime() ||
nextDate.getTime() < now - MAX_SKEW
);
Observed output:
20200101000000Z 20200102000000Z
NaN NaN
Rejected by freshness condition: false
This reproduction isolates ASN.1 decoding, date parsing and the acceptance condition. It does not construct a fully signed Apple OCSP response or demonstrate a production replay attack.
For the timezone issue, parsing 20260905033451 produces 2026-09-05T03:34:51.000Z under TZ=UTC, but 2026-09-04T22:04:51.000Z under TZ=Asia/Kolkata.
Expected behavior
Parse supported GeneralizedTime values in UTC, reject invalid timestamps and enforce the existing freshness bounds with the intended clock-skew allowance.
Impact
This is a failure to reject stale or future-dated OCSP status information when online checks are enabled. Replayed, previously signed good responses could pass the freshness gate if all other verification requirements are satisfied.
OCSP signatures, responder authorization, certificate matching, certificate validity and JWS signatures are checked separately. This finding does not establish a bypass of those checks or acceptance of arbitrary forged transactions.
Suggested change
- Parse the supported GeneralizedTime format explicitly in UTC.
- Reject malformed or non-finite timestamps before any comparison. Returning
Invalid Date alone would preserve the bypass.
- Validate calendar components rather than relying solely on
Date.UTC, which can normalize out-of-range values.
- Handle an absent
nextUpdate explicitly under a documented policy; the OCSP syntax makes this field optional.
- Add regression coverage for expired and future-dated responses, malformed dates,and timezone-independent parsing.
References and duplicate check
Description
When
enableOnlineChecksis enabled,SignedDataVerifier.checkOCSPStatusparses the matching response'sthisupdateandnextupdatefields withparseX509Date. The installed jsrsasign OCSP parser preserves the trailingZin GeneralizedTime values such as20260905033451Z.However,
parseX509Dateonly matches 14 digits with no timezone suffix:A value ending in
Zdoes not match the expression and reachesnew Dateunchanged. Node.js returnsInvalid Date, whose timestamp isNaN. Both timestamp comparisons in the following condition then evaluate to false:Consequently, an otherwise accepted OCSP response with status
goodis not rejected whennextUpdateis expired orthisUpdateis in the future. The fallback parsing of values withoutZalso uses the process's local timezone instead of UTC.Reproduction
From the repository root, after building the package, run:
Observed output:
This reproduction isolates ASN.1 decoding, date parsing and the acceptance condition. It does not construct a fully signed Apple OCSP response or demonstrate a production replay attack.
For the timezone issue, parsing
20260905033451produces2026-09-05T03:34:51.000ZunderTZ=UTC, but2026-09-04T22:04:51.000ZunderTZ=Asia/Kolkata.Expected behavior
Parse supported GeneralizedTime values in UTC, reject invalid timestamps and enforce the existing freshness bounds with the intended clock-skew allowance.
Impact
This is a failure to reject stale or future-dated OCSP status information when online checks are enabled. Replayed, previously signed
goodresponses could pass the freshness gate if all other verification requirements are satisfied.OCSP signatures, responder authorization, certificate matching, certificate validity and JWS signatures are checked separately. This finding does not establish a bypass of those checks or acceptance of arbitrary forged transactions.
Suggested change
Invalid Datealone would preserve the bypass.Date.UTC, which can normalize out-of-range values.nextUpdateexplicitly under a documented policy; the OCSP syntax makes this field optional.References and duplicate check