OCSPResponse.ts (11227B)
1 import * as asn1js from "asn1js"; 2 import * as pvutils from "pvutils"; 3 import { ResponseBytes, ResponseBytesJson, ResponseBytesSchema } from "./ResponseBytes"; 4 import { BasicOCSPResponse } from "./BasicOCSPResponse"; 5 import * as Schema from "./Schema"; 6 import { Certificate } from "./Certificate"; 7 import { id_PKIX_OCSP_Basic } from "./ObjectIdentifiers"; 8 import { AsnError } from "./errors"; 9 import { PkiObject, PkiObjectParameters } from "./PkiObject"; 10 import * as common from "./common"; 11 12 const RESPONSE_STATUS = "responseStatus"; 13 const RESPONSE_BYTES = "responseBytes"; 14 15 export interface IOCSPResponse { 16 responseStatus: asn1js.Enumerated; 17 responseBytes?: ResponseBytes; 18 } 19 20 export interface OCSPResponseJson { 21 responseStatus: asn1js.EnumeratedJson; 22 responseBytes?: ResponseBytesJson; 23 } 24 25 export type OCSPResponseParameters = PkiObjectParameters & Partial<IOCSPResponse>; 26 27 /** 28 * Represents an OCSP response described in [RFC6960 Section 4.2](https://datatracker.ietf.org/doc/html/rfc6960#section-4.2) 29 * 30 * @example The following example demonstrates how to verify OCSP response 31 * ```js 32 * const asnOcspResp = asn1js.fromBER(ocspRespRaw); 33 * const ocspResp = new pkijs.OCSPResponse({ schema: asnOcspResp.result }); 34 * 35 * if (!ocspResp.responseBytes) { 36 * throw new Error("No \"ResponseBytes\" in the OCSP Response - nothing to verify"); 37 * } 38 * 39 * const asnOcspRespBasic = asn1js.fromBER(ocspResp.responseBytes.response.valueBlock.valueHex); 40 * const ocspBasicResp = new pkijs.BasicOCSPResponse({ schema: asnOcspRespBasic.result }); 41 * const ok = await ocspBasicResp.verify({ trustedCerts: [cert] }); 42 * ``` 43 * 44 * @example The following example demonstrates how to create OCSP response 45 * ```js 46 * const ocspBasicResp = new pkijs.BasicOCSPResponse(); 47 * 48 * // Create specific TST info structure to sign 49 * ocspBasicResp.tbsResponseData.responderID = issuerCert.subject; 50 * ocspBasicResp.tbsResponseData.producedAt = new Date(); 51 * 52 * const certID = new pkijs.CertID(); 53 * await certID.createForCertificate(cert, { 54 * hashAlgorithm: "SHA-256", 55 * issuerCertificate: issuerCert, 56 * }); 57 * const response = new pkijs.SingleResponse({ 58 * certID, 59 * }); 60 * response.certStatus = new asn1js.Primitive({ 61 * idBlock: { 62 * tagClass: 3, // CONTEXT-SPECIFIC 63 * tagNumber: 0 // [0] 64 * }, 65 * lenBlockLength: 1 // The length contains one byte 0x00 66 * }); // status - success 67 * response.thisUpdate = new Date(); 68 * 69 * ocspBasicResp.tbsResponseData.responses.push(response); 70 * 71 * // Add certificates for chain OCSP response validation 72 * ocspBasicResp.certs = [issuerCert]; 73 * 74 * await ocspBasicResp.sign(keys.privateKey, "SHA-256"); 75 * 76 * // Finally create completed OCSP response structure 77 * const ocspBasicRespRaw = ocspBasicResp.toSchema().toBER(false); 78 * 79 * const ocspResp = new pkijs.OCSPResponse({ 80 * responseStatus: new asn1js.Enumerated({ value: 0 }), // success 81 * responseBytes: new pkijs.ResponseBytes({ 82 * responseType: pkijs.id_PKIX_OCSP_Basic, 83 * response: new asn1js.OctetString({ valueHex: ocspBasicRespRaw }), 84 * }), 85 * }); 86 * 87 * const ocspRespRaw = ocspResp.toSchema().toBER(); 88 * ``` 89 */ 90 export class OCSPResponse extends PkiObject implements IOCSPResponse { 91 92 public static override CLASS_NAME = "OCSPResponse"; 93 94 public responseStatus!: asn1js.Enumerated; 95 public responseBytes?: ResponseBytes; 96 97 /** 98 * Initializes a new instance of the {@link OCSPResponse} class 99 * @param parameters Initialization parameters 100 */ 101 constructor(parameters: OCSPResponseParameters = {}) { 102 super(); 103 104 this.responseStatus = pvutils.getParametersValue(parameters, RESPONSE_STATUS, OCSPResponse.defaultValues(RESPONSE_STATUS)); 105 if (RESPONSE_BYTES in parameters) { 106 this.responseBytes = pvutils.getParametersValue(parameters, RESPONSE_BYTES, OCSPResponse.defaultValues(RESPONSE_BYTES)); 107 } 108 109 if (parameters.schema) { 110 this.fromSchema(parameters.schema); 111 } 112 } 113 114 /** 115 * Returns default values for all class members 116 * @param memberName String name for a class member 117 * @returns Default value 118 */ 119 public static override defaultValues(memberName: typeof RESPONSE_STATUS): asn1js.Enumerated; 120 public static override defaultValues(memberName: typeof RESPONSE_BYTES): ResponseBytes; 121 public static override defaultValues(memberName: string): any { 122 switch (memberName) { 123 case RESPONSE_STATUS: 124 return new asn1js.Enumerated(); 125 case RESPONSE_BYTES: 126 return new ResponseBytes(); 127 default: 128 return super.defaultValues(memberName); 129 } 130 } 131 132 /** 133 * Compare values with default values for all class members 134 * @param memberName String name for a class member 135 * @param memberValue Value to compare with default value 136 */ 137 public static compareWithDefault(memberName: string, memberValue: any): boolean { 138 switch (memberName) { 139 case RESPONSE_STATUS: 140 return (memberValue.isEqual(OCSPResponse.defaultValues(memberName))); 141 case RESPONSE_BYTES: 142 return ((ResponseBytes.compareWithDefault("responseType", memberValue.responseType)) && 143 (ResponseBytes.compareWithDefault("response", memberValue.response))); 144 default: 145 return super.defaultValues(memberName); 146 } 147 } 148 149 /** 150 * @inheritdoc 151 * @asn ASN.1 schema 152 * ```asn 153 * OCSPResponse ::= SEQUENCE { 154 * responseStatus OCSPResponseStatus, 155 * responseBytes [0] EXPLICIT ResponseBytes OPTIONAL } 156 * 157 * OCSPResponseStatus ::= ENUMERATED { 158 * successful (0), -- Response has valid confirmations 159 * malformedRequest (1), -- Illegal confirmation request 160 * internalError (2), -- Internal error in issuer 161 * tryLater (3), -- Try again later 162 * -- (4) is not used 163 * sigRequired (5), -- Must sign the request 164 * unauthorized (6) -- Request unauthorized 165 * } 166 *``` 167 */ 168 public static override schema(parameters: Schema.SchemaParameters<{ 169 responseStatus?: string; 170 responseBytes?: ResponseBytesSchema; 171 }> = {}): Schema.SchemaType { 172 const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); 173 174 return (new asn1js.Sequence({ 175 name: (names.blockName || "OCSPResponse"), 176 value: [ 177 new asn1js.Enumerated({ name: (names.responseStatus || RESPONSE_STATUS) }), 178 new asn1js.Constructed({ 179 optional: true, 180 idBlock: { 181 tagClass: 3, // CONTEXT-SPECIFIC 182 tagNumber: 0 // [0] 183 }, 184 value: [ 185 ResponseBytes.schema(names.responseBytes || { 186 names: { 187 blockName: RESPONSE_BYTES 188 } 189 }) 190 ] 191 }) 192 ] 193 })); 194 } 195 196 public fromSchema(schema: Schema.SchemaType): void { 197 // Clear input data first 198 pvutils.clearProps(schema, [ 199 RESPONSE_STATUS, 200 RESPONSE_BYTES 201 ]); 202 203 // Check the schema is valid 204 const asn1 = asn1js.compareSchema(schema, 205 schema, 206 OCSPResponse.schema() 207 ); 208 AsnError.assertSchema(asn1, this.className); 209 210 // Get internal properties from parsed schema 211 this.responseStatus = asn1.result.responseStatus; 212 if (RESPONSE_BYTES in asn1.result) 213 this.responseBytes = new ResponseBytes({ schema: asn1.result.responseBytes }); 214 } 215 216 public toSchema(): asn1js.Sequence { 217 //#region Create array for output sequence 218 const outputArray = []; 219 220 outputArray.push(this.responseStatus); 221 if (this.responseBytes) { 222 outputArray.push(new asn1js.Constructed({ 223 idBlock: { 224 tagClass: 3, // CONTEXT-SPECIFIC 225 tagNumber: 0 // [0] 226 }, 227 value: [this.responseBytes.toSchema()] 228 })); 229 } 230 //#endregion 231 232 //#region Construct and return new ASN.1 schema for this object 233 return (new asn1js.Sequence({ 234 value: outputArray 235 })); 236 //#endregion 237 } 238 239 public toJSON(): OCSPResponseJson { 240 const res: OCSPResponseJson = { 241 responseStatus: this.responseStatus.toJSON() 242 }; 243 244 if (this.responseBytes) { 245 res.responseBytes = this.responseBytes.toJSON(); 246 } 247 248 return res; 249 } 250 251 /** 252 * Get OCSP response status for specific certificate 253 * @param certificate 254 * @param issuerCertificate 255 * @param crypto Crypto engine 256 */ 257 public async getCertificateStatus(certificate: Certificate, issuerCertificate: Certificate, crypto = common.getCrypto(true)) { 258 //#region Initial variables 259 let basicResponse; 260 261 const result = { 262 isForCertificate: false, 263 status: 2 // 0 = good, 1 = revoked, 2 = unknown 264 }; 265 //#endregion 266 267 //#region Check that RESPONSE_BYTES contain "OCSP_BASIC_RESPONSE" 268 if (!this.responseBytes) 269 return result; 270 271 if (this.responseBytes.responseType !== id_PKIX_OCSP_Basic) // id-pkix-ocsp-basic 272 return result; 273 274 try { 275 const asn1Basic = asn1js.fromBER(this.responseBytes.response.valueBlock.valueHexView); 276 AsnError.assert(asn1Basic, "Basic OCSP response"); 277 basicResponse = new BasicOCSPResponse({ schema: asn1Basic.result }); 278 } 279 catch { 280 return result; 281 } 282 //#endregion 283 284 return basicResponse.getCertificateStatus(certificate, issuerCertificate, crypto); 285 } 286 287 /** 288 * Make a signature for current OCSP Response 289 * @param privateKey Private key for "subjectPublicKeyInfo" structure 290 * @param hashAlgorithm Hashing algorithm. Default SHA-1 291 */ 292 public async sign(privateKey: CryptoKey, hashAlgorithm?: string, crypto = common.getCrypto(true)) { 293 //#region Check that ResponseData has type BasicOCSPResponse and sign it 294 if (this.responseBytes && this.responseBytes.responseType === id_PKIX_OCSP_Basic) { 295 const basicResponse = BasicOCSPResponse.fromBER(this.responseBytes.response.valueBlock.valueHexView as BufferSource); 296 297 return basicResponse.sign(privateKey, hashAlgorithm, crypto); 298 } 299 300 throw new Error(`Unknown ResponseBytes type: ${this.responseBytes?.responseType || "Unknown"}`); 301 //#endregion 302 } 303 304 /** 305 * Verify current OCSP Response 306 * @param issuerCertificate In order to decrease size of resp issuer cert could be omitted. In such case you need manually provide it. 307 * @param crypto Crypto engine 308 */ 309 public async verify(issuerCertificate: Certificate | null = null, crypto = common.getCrypto(true)): Promise<boolean> { 310 //#region Check that ResponseBytes exists in the object 311 if ((RESPONSE_BYTES in this) === false) 312 throw new Error("Empty ResponseBytes field"); 313 //#endregion 314 315 //#region Check that ResponseData has type BasicOCSPResponse and verify it 316 if (this.responseBytes && this.responseBytes.responseType === id_PKIX_OCSP_Basic) { 317 const basicResponse = BasicOCSPResponse.fromBER(this.responseBytes.response.valueBlock.valueHexView as BufferSource); 318 319 if (issuerCertificate !== null) { 320 if (!basicResponse.certs) { 321 basicResponse.certs = []; 322 } 323 324 basicResponse.certs.push(issuerCertificate); 325 } 326 327 return basicResponse.verify({}, crypto); 328 } 329 330 throw new Error(`Unknown ResponseBytes type: ${this.responseBytes?.responseType || "Unknown"}`); 331 //#endregion 332 } 333 334 }