Repository: PeculiarVentures/PKI.js

Issue:
Request.schema() validates singleRequestExtensions as a single Extension instead of [0] EXPLICIT Extensions
singleRequestExtensions is defined in RFC 6960 §4.1.1 as [0] EXPLICIT Extensions, but the current Request.schema() validates it as a single Extension.

Expected: singleRequestExtensions is a context-specific constructed tag [0] wrapping an Extensions SEQUENCE (SEQUENCE OF Extension).
Actual: It’s validated as a single Extension, which rejects otherwise valid requests and leads to:

> Error: Object's schema was not verified against input data for OCSPRequest

Note: toSchema() already emits an Extensions SEQUENCE under [0], creating an encode/decode mismatch. I have a patch ready that switches the validator from Extension.schema to Extensions.schema.

Discussion:

Solution patch:
diff --git a/src/Request.ts b/src/Request.ts
index e3397e4..3e5f83e 100644
--- a/src/Request.ts
+++ b/src/Request.ts
@@ -3,7 +3,8 @@ import * as pvutils from "pvutils";
 import { CertID, CertIDJson, CertIDSchema } from "./CertID";
 import { EMPTY_STRING } from "./constants";
 import { AsnError } from "./errors";
-import { Extension, ExtensionJson, ExtensionSchema } from "./Extension";
+import { Extension, ExtensionJson } from "./Extension";
+import { Extensions, ExtensionsSchema } from "./Extensions";
 import { PkiObject, PkiObjectParameters } from "./PkiObject";
 import * as Schema from "./Schema";
 
@@ -28,7 +29,7 @@ export type RequestParameters = PkiObjectParameters & Partial<IRequest>;
 
 export type RequestSchema = Schema.SchemaParameters<{
   reqCert?: CertIDSchema;
-  extensions?: ExtensionSchema;
+  extensions?: ExtensionsSchema;
   singleRequestExtensions?: string;
 }>;
 
@@ -115,7 +116,7 @@ export class Request extends PkiObject implements IRequest {
             tagClass: 3, // CONTEXT-SPECIFIC
             tagNumber: 0 // [0]
           },
-          value: [Extension.schema(names.extensions || {
+          value: [Extensions.schema(names.extensions || {
             names: {
               blockName: (names.singleRequestExtensions || EMPTY_STRING)
             }
diff --git a/src/SingleResponse.ts b/src/SingleResponse.ts
index 0d3c826..eff1b7d 100644
--- a/src/SingleResponse.ts
+++ b/src/SingleResponse.ts
@@ -274,8 +274,12 @@ export class SingleResponse extends PkiObject implements ISingleResponse {
     }
 
     if (this.singleExtensions) {
-      outputArray.push(new asn1js.Sequence({
-        value: Array.from(this.singleExtensions, o => o.toSchema())
+      outputArray.push(new asn1js.Constructed({
+        idBlock: {
+          tagClass: 3, // CONTEXT-SPECIFIC
+          tagNumber: 1 // [1]
+        },
+        value: [new asn1js.Sequence({ value: Array.from(this.singleExtensions, o => o.toSchema()) })]
       }));
     }
 



Test patch:
diff --git a/test/ocspRequestComplexExample.ts b/test/ocspRequestComplexExample.ts
index b2b4752..4dd8610 100644
--- a/test/ocspRequestComplexExample.ts
+++ b/test/ocspRequestComplexExample.ts
@@ -33,6 +33,17 @@ export async function createOCSPReq(): Promise<ArrayBuffer> {
   fictionView[2] = 0x02;
   fictionView[3] = 0x03;
 
+  const issuerName = new pkijs.RelativeDistinguishedNames({
+    typesAndValues: [
+      new pkijs.AttributeTypeAndValue({
+        type: "2.5.4.3", // CN
+        value: new asn1js.Utf8String({ value: "test-issuer" }),
+      }),
+    ],
+  });
+  const serviceLocatorDer = new asn1js.Sequence({
+    value: [issuerName.toSchema()],
+  }).toBER(false);
   ocspReqSimpl.tbsRequest.requestList = [new pkijs.Request({
     reqCert: new pkijs.CertID({
       hashAlgorithm: new pkijs.AlgorithmIdentifier({
@@ -41,7 +52,14 @@ export async function createOCSPReq(): Promise<ArrayBuffer> {
       issuerNameHash: new asn1js.OctetString({ valueHex: fictionBuffer }),
       issuerKeyHash: new asn1js.OctetString({ valueHex: fictionBuffer }),
       serialNumber: new asn1js.Integer({ valueHex: fictionBuffer })
-    })
+    }),
+    singleRequestExtensions: [
+      new pkijs.Extension({
+        extnID: "1.3.6.1.5.5.7.48.1.7", // id-pkix-ocsp-service-locator
+        critical: false,
+        extnValue: serviceLocatorDer,
+      }),
+    ]
   })];
 
   ocspReqSimpl.tbsRequest.requestExtensions = [
diff --git a/test/ocspResponseComplexExample.ts b/test/ocspResponseComplexExample.ts
index 5d784cd..f2e67ad 100644
--- a/test/ocspResponseComplexExample.ts
+++ b/test/ocspResponseComplexExample.ts
@@ -41,6 +41,15 @@ export async function createOCSPResp(hashAlg: string, signAlg: string): Promise<
     },
   }); // status - success
   response.thisUpdate = new Date();
+  response.nextUpdate = new Date(new Date().getTime() + 24 * 60 * 60 * 1000); // Next day
+  const archiveCutoffDate = new Date(Date.now() - 7 * 365 * 24 * 60 * 60 * 1000); // 7 years ago
+  response.singleExtensions = [
+    new pkijs.Extension({
+      extnID: "1.3.6.1.5.5.7.48.1.6", // id-pkix-ocsp-archive-cutoff
+      critical: false,
+      extnValue: (new asn1js.GeneralizedTime({ valueDate: archiveCutoffDate })).toBER(false)
+    })
+  ];
 
   ocspBasicResp.tbsResponseData.responses.push(response);