import * as crypto from 'crypto' ; import rs = require ( 'jsrsasign' ) ; import { KNOWN_PINS } from './pinningData' ; export interface PinningResult { certificate ? : ElectronCertificate ; decoding ? : boolean ; errorMessage ? : string ; fingerprintCheck ? : boolean ; verifiedIssuerRootCerts ? : boolean ; verifiedPublicKeyInfo ? : boolean ; } export interface ElectronCertificate { data : string ; fingerprint ? : string ; issuer ? : CertificatePrincipal ; issuerCert ? : ElectronCertificate ; issuerName ? : string ; serialNumber ? : string ; subject ? : CertificatePrincipal ; subjectName ? : string ; validExpiry ? : number ; validStart ? : number ; } interface CertificatePrincipal { commonName : string ; country : string ; locality : string ; organizations : string [ ] ; organizationUnits : string [ ] ; state : string ; } export function buildCert ( buffer ) { return `-----BEGIN CERTIFICATE-----\n${ buffer . toString ( 'base64' ) }\n-----END CERTIFICATE-----` ; } export function getFingerprint ( derCert ) { const derBinary = derCert . toString ( 'binary' ) ; const hexDerFileContents = rs . rstrtohex ( derBinary ) ; const pemString = rs . KJUR . asn1 . ASN1Util . getPEMStringFromHex ( hexDerFileContents , 'CERTIFICATE' ) ; const publicKey = rs . X509 . getPublicKeyInfoPropOfCertPEM ( pemString ) ; const publicKeyBytes = Buffer . from ( publicKey . keyhex , 'hex' ) . toString ( 'binary' ) ; return crypto . createHash ( 'sha256' ) . update ( publicKeyBytes ) . digest ( 'base64' ) ; } export function hostnameShouldBePinned ( hostname ) { return KNOWN_PINS . some ( pin => pin . url . test ( hostname . toLowerCase ( ) . trim ( ) ) ) ; } export function verifyPinning ( hostname , remoteCertificate ? ) { if ( ! remoteCertificate ) { return { errorMessage : 'No certificate provided by Electron.' , } ; } if ( ! remoteCertificate . issuerCert ) { return { errorMessage : 'No issuer certificate in certificate.' , } ; } const { data : certData , issuerCert } = remoteCertificate ; function getRemoteIssuerCertData ( remoteIssuerCert = issuerCert ) { return remoteIssuerCert . issuerCert ? getRemoteIssuerCertData ( remoteIssuerCert . issuerCert ) : remoteIssuerCert ; } let remoteIssuerCertHex ; let remotePublicKey ; let remotePublicKeyBytes ; let remotePublicKeyFingerprint ; const errorMessages = [ ] ; try { remoteIssuerCertHex = rs . pemtohex ( getRemoteIssuerCertData ( ) . data ) ; remotePublicKey = rs . X509 . getPublicKeyInfoPropOfCertPEM ( certData ) ; remotePublicKeyBytes = Buffer . from ( remotePublicKey . keyhex , 'hex' ) . toString ( 'binary' ) ; remotePublicKeyFingerprint = crypto . createHash ( 'sha256' ) . update ( remotePublicKeyBytes ) . digest ( 'base64' ) ; } catch ( error ) { return { decoding : false , errorMessage : ( error as Error ) . message , } ; } const result = { } ; for ( const knownPin of KNOWN_PINS ) { const { url , publicKeyInfo : localPublicKeyInfo = [ ] , issuerRootCerts : knownIssuerRootCerts = [ ] } = knownPin ; if ( url . test ( hostname . toLowerCase ( ) . trim ( ) ) ) { if ( knownIssuerRootCerts . length > 0 ) { result . verifiedIssuerRootCerts = knownIssuerRootCerts . some ( knownRootCert => remoteIssuerCertHex === rs . pemtohex ( knownRootCert ) ) ; if ( ! result . verifiedIssuerRootCerts ) { const knownCertsCombined = knownIssuerRootCerts . join ( ', ' ) . replace ( /[\r\n]/g , '' ) ; const errorMessage = `Issuer root certificates: none of "${ knownCertsCombined }" could be verified against "${ getRemoteIssuerCertData ( ) . data }.` ; errorMessages . push ( errorMessage ) ; } } result . verifiedPublicKeyInfo = localPublicKeyInfo . reduce ( ( arr , localPubKey ) => { const { algorithmID : localAlgorithmID , algorithmParam : localAlgorithmParam , fingerprints : localFingerprints , } = localPubKey ; const fingerprintCheck = localFingerprints . length > 0 && localFingerprints . some ( knownFingerprint => knownFingerprint === remotePublicKeyFingerprint ) ; const algorithmIDCheck = localAlgorithmID === remotePublicKey . algoid ; const algorithmParamCheck = localAlgorithmParam === remotePublicKey . algparam ; if ( ! fingerprintCheck ) { const fingerprintsCombined = localFingerprints . join ( ', ' ) ; const errorMessage = `Public key fingerprints: "${ remotePublicKeyFingerprint }" could not be verified with any of the known fingerprints "${ fingerprintsCombined }".` ; errorMessages . push ( errorMessage ) ; } if ( ! algorithmIDCheck ) { const algorithmID = remotePublicKey . algoid ; const errorMessage = `Algorithm ID: "${ algorithmID }" could not be verified with the known ID "${ localAlgorithmID }".` ; errorMessages . push ( errorMessage ) ; } if ( ! algorithmParamCheck ) { const algorithmParam = remotePublicKey . algparam ; const errorMessage = `Algorithm parameter: "${ algorithmParam }" could not be verified with the known parameter "${ localAlgorithmParam }".` ; errorMessages . push ( errorMessage ) ; } arr . push ( fingerprintCheck , algorithmIDCheck , algorithmParamCheck ) ; return arr ; } , [ ] ) . every ( value => Boolean ( value ) ) ; break ; } } if ( errorMessages . length > 0 ) { result . errorMessage = errorMessages . join ( '\n' ) ; result . certificate = remoteCertificate ; } return result ; }