11'use strict' ;
22
33const crypto = require ( 'crypto' ) ;
4+ const zlib = require ( 'zlib' ) ;
5+ const { promisify } = require ( 'util' ) ;
46const { AsnParser } = require ( '@peculiar/asn1-schema' ) ;
57const { LogotypeExtn } = require ( '@peculiar/asn1-x509-logotype' ) ;
68const { Certificate } = require ( '@peculiar/asn1-x509' ) ;
7- const zlib = require ( 'zlib' ) ;
8- const util = require ( 'util' ) ;
9- const gunzip = util . promisify ( zlib . gunzip ) ;
109
11- const hashAlgos = {
10+ const gunzip = promisify ( zlib . gunzip ) ;
11+
12+ const HASH_ALGORITHMS = {
1213 '1.3.14.3.2.26' : 'sha1' ,
1314 '2.16.840.1.101.3.4.2.4' : 'sha224' ,
1415 '2.16.840.1.101.3.4.2.1' : 'sha256' ,
@@ -19,93 +20,95 @@ const hashAlgos = {
1920} ;
2021
2122function parseDataUrl ( url ) {
22- if ( url . indexOf ( 'data:' ) !== 0 ) {
23+ if ( ! url . startsWith ( 'data:' ) ) {
2324 return { } ;
2425 }
2526
26- let commaPos = url . indexOf ( ',' ) ;
27+ const commaPos = url . indexOf ( ',' ) ;
2728 if ( commaPos < 0 ) {
2829 return { } ;
2930 }
3031
31- let header = url . substring ( 'data:' . length , commaPos ) ;
32- let content = url . substring ( commaPos + 1 ) ;
33-
34- let parts = header . split ( ';' ) ;
35- let mediaType = parts . shift ( ) || '' ;
36- let base64 = parts . shift ( ) === 'base64' ;
32+ const header = url . substring ( 5 , commaPos ) ;
33+ const content = url . substring ( commaPos + 1 ) ;
34+ const parts = header . split ( ';' ) ;
35+ const mediaType = parts . shift ( ) || '' ;
36+ const base64 = parts . shift ( ) === 'base64' ;
3737
3838 return { header, content, mediaType, base64 } ;
3939}
4040
41+ function extractHashInfo ( imageDetails ) {
42+ const hashObject = imageDetails . logotypeHash ?. [ 0 ] ;
43+ if ( ! hashObject ) {
44+ return { hashAlgo : undefined , hashValue : undefined } ;
45+ }
46+
47+ const hashAlgoOid = hashObject . hashAlg ?. algorithm ;
48+ const hashAlgo = hashAlgoOid ? HASH_ALGORITHMS [ hashAlgoOid ] : undefined ;
49+ const hashValue = hashObject . hashValue ?. buffer ? Buffer . from ( hashObject . hashValue . buffer ) : undefined ;
50+
51+ return { hashAlgo, hashValue } ;
52+ }
53+
54+ async function extractLogoFile ( imageDetails ) {
55+ const logotypeURI = imageDetails . logotypeURI ?. [ 0 ] ;
56+ if ( ! logotypeURI ) {
57+ return undefined ;
58+ }
59+
60+ const { content, base64 } = parseDataUrl ( logotypeURI ) ;
61+ if ( ! content ) {
62+ return undefined ;
63+ }
64+
65+ const data = base64 ? Buffer . from ( content , 'base64' ) : content ;
66+ return gunzip ( data ) ;
67+ }
68+
69+ function validateHash ( logoFile , hashAlgo , hashValue ) {
70+ if ( ! logoFile || ! hashAlgo || ! hashValue ) {
71+ return undefined ;
72+ }
73+
74+ try {
75+ const hasher = crypto . createHash ( hashAlgo ) ;
76+ return hashValue . equals ( hasher . update ( logoFile ) . digest ( ) ) ;
77+ } catch {
78+ return undefined ;
79+ }
80+ }
81+
4182async function parseVMC ( buf ) {
4283 const cert = AsnParser . parse ( buf , Certificate ) ;
4384 const logoExtension = cert . tbsCertificate . extensions . find ( ext => ext . extnID === '1.3.6.1.5.5.7.1.12' ) ;
4485
45- if ( logoExtension && logoExtension . extnValue ) {
46- let logo = AsnParser . parse ( logoExtension . extnValue , LogotypeExtn ) ;
47-
48- let imageDetails =
49- logo . subjectLogo &&
50- logo . subjectLogo . direct &&
51- logo . subjectLogo . direct . image &&
52- logo . subjectLogo . direct . image [ 0 ] &&
53- logo . subjectLogo . direct . image [ 0 ] . imageDetails ;
54-
55- if ( imageDetails ) {
56- let hashAlgo , hashValue , hashAlgoOid ;
57-
58- let hashObject = imageDetails . logotypeHash && imageDetails . logotypeHash [ 0 ] && imageDetails . logotypeHash [ 0 ] ;
59-
60- if ( hashObject ) {
61- hashAlgoOid = hashObject . hashAlg && hashObject . hashAlg . algorithm ;
62- if ( hashAlgoOid && hashAlgos [ hashAlgoOid ] ) {
63- hashAlgo = hashAlgos [ hashAlgoOid ] ;
64- }
65-
66- if ( hashObject . hashValue && hashObject . hashValue . buffer ) {
67- hashValue = Buffer . from ( hashObject . hashValue . buffer ) ;
68- }
69- }
70-
71- let logotypeURI = imageDetails . logotypeURI && imageDetails . logotypeURI [ 0 ] ;
72- let logoFile ;
73- if ( logotypeURI ) {
74- let { content, base64 } = parseDataUrl ( logotypeURI ) ;
75- if ( content ) {
76- if ( base64 ) {
77- content = Buffer . from ( content , 'base64' ) ;
78- }
79- logoFile = await gunzip ( content ) ;
80- }
81- }
82-
83- let data = {
84- mediaType : imageDetails . mediaType ,
85- hashAlgo,
86- hashValue,
87- logoFile
88- } ;
89-
90- if ( data . logoFile && ( data . hashAlgo || data . hashAlgoOid ) && data . hashValue ) {
91- let hasher ;
92- try {
93- hasher = crypto . createHash ( data . hashAlgo || data . hashAlgoOid ) ;
94- } catch {
95- // invalid hash
96- }
97- if ( hasher ) {
98- data . validHash = hashValue . equals ( hasher . update ( logoFile ) . digest ( ) ) ;
99- }
100- }
101-
102- return data ;
103- }
86+ if ( ! logoExtension ?. extnValue ) {
87+ const error = new Error ( 'Invalid or missing logotype extension in the certificate' ) ;
88+ error . code = 'INVALID_LOGOTYPE_EXT' ;
89+ throw error ;
90+ }
91+
92+ const logo = AsnParser . parse ( logoExtension . extnValue , LogotypeExtn ) ;
93+ const imageDetails = logo . subjectLogo ?. direct ?. image ?. [ 0 ] ?. imageDetails ;
94+
95+ if ( ! imageDetails ) {
96+ const error = new Error ( 'Invalid or missing logotype extension in the certificate' ) ;
97+ error . code = 'INVALID_LOGOTYPE_EXT' ;
98+ throw error ;
10499 }
105100
106- let error = new Error ( 'Invalid or missing logotype extension in the certificate' ) ;
107- error . code = 'INVALID_LOGOTYPE_EXT' ;
108- throw error ;
101+ const { hashAlgo, hashValue } = extractHashInfo ( imageDetails ) ;
102+ const logoFile = await extractLogoFile ( imageDetails ) ;
103+ const validHash = validateHash ( logoFile , hashAlgo , hashValue ) ;
104+
105+ return {
106+ mediaType : imageDetails . mediaType ,
107+ hashAlgo,
108+ hashValue,
109+ logoFile,
110+ validHash
111+ } ;
109112}
110113
111114module . exports = { parseVMC } ;
0 commit comments