"scp/git@code.vereign.com:docker/osticket.git" did not exist on "f1dc3dd4cf1ae9e3e0e9adcc7622304d05e1cf7e"
Newer
Older
import {
canTryPincode,
failPincodeAttempt,
getTimeLeftInLocalStorage,
makeid
} from "./appUtility";
import {
bufferToHexCodes,
stringToArrayBuffer,
isEqualBuffer
} from "pvutils";
import { fromBER } from "asn1js";
import { ContentInfo, SignedData } from "pkijs";
import { algomap, rdnmap } from "../constants/certificates";
import {
fixNewLines,
getAttachment,
getAttachments,
getGlobalHeaderValue,
getHeaderValue,
parseMIME
} from "../helpers/mailparser";
import dataUriToBlob from "data-uri-to-blob";
import {
extractHtmlBodyFromString,
getFilenameFromHeaders,
SIGNATURE_CONTENT_TYPE
} from "./emailUtilities";
const libmime = require("libmime");
const pkijs = require("pkijs");
const asn1js = require("asn1js");
const pvutils = require("pvutils");
//*********************************************************************************
const CERTIFIATE_Version_1 = 0;
const CERTIFIATE_Version_3 = 2;
//these bit fields are reversed, WTF!
const KEY_USAGE_DigitalSignature = 0x80; //01;
const KEY_USAGE_NonRepudiation = 0x40; //02;
const KEY_USAGE_KeyEncipherment = 0x20; //04;
const KEY_USAGE_DataEncipherment = 0x10; //08;
const KEY_USAGE_KeyAgreement = 0x08; //10;
const KEY_USAGE_KeyCertSign = 0x04; //20;
const KEY_USAGE_CRLSign = 0x02; //40;
//const KEY_USAGE_EncipherOnly = 0x01;//80; // Not used for now. Must be used together with KEY_USAGE_KeyAgreement (maybe should be ORed as a constant directly?)
//const KEY_USAGE_DecipherOnly = 0x80;//0100; // If used, modify "KeyUsage" extension array buffer size and appropriate bit operators to accomodate for extra byte
const KEY_USAGE_LeafCertificate =
KEY_USAGE_DigitalSignature |
KEY_USAGE_NonRepudiation |
KEY_USAGE_KeyEncipherment |
KEY_USAGE_DataEncipherment;
const KEY_USAGE_CertificateAuthority =
KEY_USAGE_DigitalSignature | KEY_USAGE_KeyCertSign | KEY_USAGE_CRLSign;
const OID_EXT_KEY_USAGE_Any = "2.5.29.37.0";
const OID_ID_PKIX_ServerAuth = "1.3.6.1.5.5.7.3.1";
const OID_ID_PKIX_ClientAuth = "1.3.6.1.5.5.7.3.2";
const OID_ID_PKIX_CodeSigning = "1.3.6.1.5.5.7.3.3";
const OID_ID_PKIX_EmailProtection = "1.3.6.1.5.5.7.3.4";
const OID_ID_PKIX_TimeStamping = "1.3.6.1.5.5.7.3.8";
const OID_ID_PKIX_OCSPSigning = "1.3.6.1.5.5.7.3.9";
// const OID_EXT_KEY_USAGE_MS... = "1.3.6.1.4.1.311.10.3.1"; // Microsoft Certificate Trust List signing
// const OID_EXT_KEY_USAGE_MS... = "1.3.6.1.4.1.311.10.3.4"; // Microsoft Encrypted File System
const OID_PKCS7_Data = "1.2.840.113549.1.7.1";
const OID_PKCS7_SignedData = "1.2.840.113549.1.7.2";
const OID_PKCS7_EnvelopedData = "1.2.840.113549.1.7.3";
const OID_PKCS9_EmailAddress = "1.2.840.113549.1.9.1";
const OID_PKCS9_ContentType = "1.2.840.113549.1.9.3";
const OID_PKCS9_MessageDigest = "1.2.840.113549.1.9.4";
const OID_PKCS9_SigningTime = "1.2.840.113549.1.9.5";
const defaultAlgorithms = {
hashAlg: "SHA-256",
signAlg: "RSASSA-PKCS1-v1_5",
keyLength: 2048
};
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
const encryptionAlgorithm = {
name: "AES-CBC",
length: 128
};
//*********************************************************************************
// Returns promise, resolved to keyPair object {publicKey, privateKey}
//*********************************************************************************
function generateKeys(algorithms) {
//region Get a "crypto" extension
const crypto = pkijs.getCrypto();
if (typeof crypto === "undefined") {
return Promise.reject("No WebCrypto extension found");
}
//endregion Get a "crypto" extension
if (!algorithms) {
algorithms = defaultAlgorithms;
} else {
if (!algorithms.hashAlg) {
algorithms.hashAlg = defaultAlgorithms.hashAlg;
}
if (!algorithms.signAlg) {
algorithms.signAlg = defaultAlgorithms.signAlg;
}
if (!algorithms.keyLength) {
algorithms.keyLength = defaultAlgorithms.keyLength;
}
}
//region Get default algorithm parameters for key generation
const algorithm = pkijs.getAlgorithmParameters(
algorithms.signAlg,
"generatekey"
);
if ("hash" in algorithm.algorithm) {
algorithm.algorithm.hash.name = algorithms.hashAlg;
}
algorithm.algorithm.modulusLength = algorithms.keyLength;
//endregion
return crypto.generateKey(algorithm.algorithm, true, algorithm.usages);
}
function fixPkijsRDN() {
pkijs.RelativeDistinguishedNames.prototype.toSchema = function () {
//region Decode stored TBS value
if (this.valueBeforeDecode.byteLength === 0) // No stored encoded array, create "from scratch"
{
return (new asn1js.Sequence({
value: Array.from(this.typesAndValues, element => new asn1js.Set({value: [element.toSchema()]}))
}));
}
const asn1 = asn1js.fromBER(this.valueBeforeDecode);
//endregion
//region Construct and return new ASN.1 schema for this object
return asn1.result;
//endregion
};
}
//*********************************************************************************
if (typeof certData === "undefined") {
return Promise.reject("No Certificate data provided");
}
if (typeof certData.subject === "undefined") {
return Promise.reject("No Certificate subject data provided");
}
//region Get a "crypto" extension
const crypto = pkijs.getCrypto();
if (typeof crypto === "undefined") {
return Promise.reject("No WebCrypto extension found");
}
//endregion Get a "crypto" extension
//region Initial variables
let sequence = Promise.resolve();
const certificate = new pkijs.Certificate();
let publicKey;
let privateKey;
let certificateBuffer; // = new ArrayBuffer(0); // ArrayBuffer with loaded or created CERT
let privateKeyBuffer; // = new ArrayBuffer(0);
let publicKeyBuffer; // = new ArrayBuffer(0);
//endregion Initial variables
if (certData.keyPair) {
//region Create a new key pair
return certData.keyPair;
});
//endregion Create a new key pair
} else {
//region Create a new key pair
return generateKeys(certData.algorithms);
});
//endregion Create a new key pair
}
//region Store new key in an interim variables
sequence = sequence.then(
keyPair => {
publicKey = keyPair.publicKey;
privateKey = keyPair.privateKey;
},
error => Promise.reject(`Error during key generation: ${error}`)
);
//endregion Store new key in an interim variables
//region Exporting public key into "subjectPublicKeyInfo" value of certificate
sequence = sequence.then(() =>
certificate.subjectPublicKeyInfo.importKey(publicKey)
);
//endregion Exporting public key into "subjectPublicKeyInfo" value of certificate
sequence = sequence.then(
() =>
crypto.digest(
{ name: "SHA-1" },
certificate.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHex
),
error => Promise.reject(`Error during importing public key: ${error}`)
);
//region Fill in cert data
//region Put a static values
certificate.version = CERTIFIATE_Version_3;
const serialNumberBuffer = new ArrayBuffer(20);
const serialNumberView = new Uint8Array(serialNumberBuffer);
pkijs.getRandomValues(serialNumberView);
// noinspection JSUnresolvedFunction
certificate.serialNumber = new asn1js.Integer({
valueHex: serialNumberView
});
//endregion Put a static values
//region Subject
// For reference http://oidref.com/2.5.4.3
if (certData.subject.commonName) {
// noinspection JSUnresolvedFunction
certificate.subject.typesAndValues.push(
new pkijs.AttributeTypeAndValue({
type: "2.5.4.3", // Common name
value: new asn1js.PrintableString({
value: certData.subject.commonName
})
})
);
}
if (certData.subject.country) {
// noinspection JSUnresolvedFunction
certificate.subject.typesAndValues.push(
new pkijs.AttributeTypeAndValue({
type: "2.5.4.6", // Country name
value: new asn1js.PrintableString({ value: certData.subject.country })
})
);
}
if (certData.subject.locality) {
// noinspection JSUnresolvedFunction
certificate.subject.typesAndValues.push(
new pkijs.AttributeTypeAndValue({
type: "2.5.4.7", // Locality Name
value: new asn1js.PrintableString({
value: certData.subject.locality
})
})
);
}
if (certData.subject.state) {
// noinspection JSUnresolvedFunction
certificate.subject.typesAndValues.push(
new pkijs.AttributeTypeAndValue({
type: "2.5.4.8", // State or Province name
value: new asn1js.PrintableString({ value: certData.subject.state })
})
);
}
if (certData.subject.organization) {
// noinspection JSUnresolvedFunction
certificate.subject.typesAndValues.push(
new pkijs.AttributeTypeAndValue({
type: "2.5.4.10", // Organization name
value: new asn1js.PrintableString({
value: certData.subject.organization
})
})
);
}
if (certData.subject.organizationUnit) {
// noinspection JSUnresolvedFunction
certificate.subject.typesAndValues.push(
new pkijs.AttributeTypeAndValue({
type: "2.5.4.11", // Organization unit name
value: new asn1js.PrintableString({
value: certData.subject.organizationUnit
})
})
);
}
if (certData.subject.email) {
// noinspection JSUnresolvedFunction
certificate.subject.typesAndValues.push(
new pkijs.AttributeTypeAndValue({
type: OID_PKCS9_EmailAddress, // Email, deprecated but still widely used
value: new asn1js.IA5String({ value: certData.subject.email })
})
);
}
//endregion Subject
//region Issuer
if (issuerData && issuerData.certificate) {
certificate.issuer = issuerData.certificate.subject;
} else {
certificate.issuer = certificate.subject;
}
//endregion Issuer
//region Validity
if (!certData.validity) {
}
if (certData.validity.notBefore) {
certificate.notBefore.value = certData.validity.notBefore; //date
} else {
const tmp = new Date();
certificate.notBefore.value = new Date(
tmp.getFullYear(),
tmp.getMonth(),
tmp.getDate(),
0,
0,
0
);
}
if (certData.validity.notAfter) {
certificate.notAfter.value = certData.validity.notAfter; //date
} else {
const tmp = certificate.notBefore.value;
const validYears = certData.validity.validYears || 1;
certificate.notAfter.value = new Date(
tmp.getFullYear() + validYears,
tmp.getMonth(),
tmp.getDate(),
23,
59,
59
);
}
//endregion Validity
//region Extensions
certificate.extensions = []; // Extensions are not a part of certificate by default, it's an optional array
//region "BasicConstraints" extension
const basicConstr = new pkijs.BasicConstraints({
//pathLenConstraint: 0 //TODO add logic for leaf CA
});
certificate.extensions.push(
new pkijs.Extension({
extnID: "2.5.29.19",
critical: true,
extnValue: basicConstr.toSchema().toBER(false),
parsedValue: basicConstr // Parsed value for well-known extensions
})
);
//endregion "BasicConstraints" extension
//region "KeyUsage" extension
const keyUsageBuffer = new ArrayBuffer(1);
const keyUsageBitView = new Uint8Array(keyUsageBuffer);
keyUsageBitView[0] = !!certData.isCA
? KEY_USAGE_CertificateAuthority
: KEY_USAGE_LeafCertificate;
// noinspection JSUnresolvedFunction
const keyUsage = new asn1js.BitString({ valueHex: keyUsageBuffer });
certificate.extensions.push(
new pkijs.Extension({
extnID: "2.5.29.15",
critical: true,
extnValue: keyUsage.toBER(false),
parsedValue: keyUsage // Parsed value for well-known extensions
})
);
//endregion "KeyUsage" extension
//region "ExtKeyUsage" extension
if (!certData.isCA && certData.subject.email) {
const extKeyUsage = new pkijs.ExtKeyUsage({
certificate.extensions.push(
new pkijs.Extension({
extnID: "2.5.29.37",
critical: false,
extnValue: extKeyUsage.toSchema().toBER(false),
parsedValue: extKeyUsage // Parsed value for well-known extensions
})
);
}
//endregion "ExtKeyUsage" extension
//region "SubjAltName" extension
if (certData.subject.email || certData.subject.url) {
const names = [];
if (certData.subject.email) {
names.push(
new pkijs.GeneralName({
type: 1, // rfc822Name
value: certData.subject.email
})
);
}
if (certData.subject.url) {
names.push(
new pkijs.GeneralName({
type: 2, // dNSName
value: certData.subject.url
})
);
}
const subjAltNames = new pkijs.GeneralNames({
names: names
});
certificate.extensions.push(
new pkijs.Extension({
extnID: "2.5.29.17",
critical: false,
extnValue: subjAltNames.toSchema().toBER(false),
parsedValue: subjAltNames // Parsed value for well-known extensions
})
);
}
//endregion "SubjAltName" extension
//region "SubjectKeyIdentifier" extension
const subjKeyId = new asn1js.OctetString({ valueHex: subjKeyIdBuffer });
certificate.extensions.push(
new pkijs.Extension({
extnID: "2.5.29.14",
critical: false,
extnValue: subjKeyId.toBER(false),
parsedValue: subjKeyId // Parsed value for well-known extensions
})
);
//endregion "SubjectKeyIdentifier" extension
/* COULD NOT GET IT WORKING
//region "AuthorityKeyIdentifier" extension
if (issuerData && issuerData.certificate) {
let issuerSubjKeyExt = null;
let extLength = issuerData.certificate.extensions.length;
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
let ext = issuerData.certificate.extensions[i];
if (ext.extnID == "2.5.29.14") {
issuerSubjKeyExt = ext;
break;
}
}
if (issuerSubjKeyExt) {
const asn1 = asn1js.fromBER(issuerSubjKeyExt.extnValue);
const authKeyIdentifier = new AuthorityKeyIdentifier({
keyIdentifier: new asn1js.OctetString({
//isHexOnly: true,
//valueHex: issuerSubjKeyExt.parsedValue.valueBlock.valueHex
value: new asn1js.OctetString({ valueHex: subjKeyIdBuffer })
})
});
// const authKeyIdentifier = new AuthorityKeyIdentifier({
// //keyIdentifier: new asn1js.OctetString({ valueHex: subjKeyIdBuffer })
// });
certificate.extensions.push(new Extension({
extnID: "2.5.29.35",
critical: false,
extnValue: authKeyIdentifier.toSchema().toBER(false),
parsedValue: authKeyIdentifier // Parsed value for well-known extensions
}));
}
}
//endregion "AuthorityKeyIdentifier" extension
*/
//endregion Extensions
});
//region Fill in cert data
//region Signing final certificate
sequence = sequence.then(
() => {
let signerKey =
issuerData && issuerData.privateKey
? issuerData.privateKey
: privateKey;
return certificate.sign(
signerKey,
certData.algorithms && certData.algorithms.hashAlg
? certData.algorithms.hashAlg
: defaultAlgorithms.hashAlg
);
error => Promise.reject(`Error during exporting public key: ${error}`)
);
//endregion
//region Encode and store certificate
sequence = sequence.then(
() => {
certificateBuffer = certificate.toSchema(true).toBER(false);
},
error => Promise.reject(`Error during signing: ${error}`)
);
//endregion
//region Exporting public key
sequence = sequence.then(() => crypto.exportKey("spki", publicKey));
//endregion
//region Store exported public key on Web page
sequence = sequence.then(
result => {
publicKeyBuffer = result;
},
error => Promise.reject(`Error during exporting of public key: ${error}`)
);
//endregion
//region Exporting private key
sequence = sequence.then(() => crypto.exportKey("pkcs8", privateKey));
//endregion
//region Store exported key on Web page
sequence = sequence.then(
result => {
privateKeyBuffer = result;
},
error => Promise.reject(`Error during exporting of private key: ${error}`)
);
//endregion
return sequence.then(() => {
const result = {
certificate: certificate,
certificatePEM: encodePEM(certificateBuffer, "CERTIFICATE"),
publicKey: publicKey,
publicKeyPEM: encodePEM(publicKeyBuffer, "PUBLIC KEY"),
privateKey: privateKey,
privateKeyPEM: encodePEM(privateKeyBuffer, "PRIVATE KEY")
};
return result;
});
}
function formatPEM(pemString) {
const lineWidth = 64;
let resultString = "";
let start = 0;
let piece;
while ((piece = pemString.substring(start, start + lineWidth)).length > 0) {
start += lineWidth;
}
return resultString;
}
function encodePEM(buffer, label) {
const bufferString = String.fromCharCode.apply(null, new Uint8Array(buffer));
const header = `-----BEGIN ${label}-----\r\n`;
const base64formatted = formatPEM(window.btoa(bufferString));
const footer = `-----END ${label}-----\r\n`;
const resultString = header + base64formatted + footer;
return resultString;
}
function decodePEM(pemString) {
const pemStripped = pemString.replace(
/(-----(BEGIN|END) [a-zA-Z ]*-----|\r|\n)/g,
""
);
const pemDecoded = window.atob(pemStripped);
const buffer = pvutils.stringToArrayBuffer(pemDecoded);
return buffer;
}
//*********************************************************************************
export function parseCertificate(certificatePEM) {
const certificateBuffer = decodePEM(certificatePEM);
const asn1 = asn1js.fromBER(certificateBuffer);
const certificate = new pkijs.Certificate({ schema: asn1.result });
return certificate;
}
//*********************************************************************************
export function encryptMessage(message, password, label) {
const buffer = pvutils.stringToArrayBuffer(message);
const secret = pvutils.stringToArrayBuffer(password);
const enveloped = new pkijs.EnvelopedData();
enveloped.addRecipientByPreDefinedData(
secret,
{},
AES_encryptionVariant_Password
);
return enveloped.encrypt(encryptionAlgorithm, buffer).then(
() => {
const content = new pkijs.ContentInfo();
content.contentType = OID_PKCS7_EnvelopedData;
content.content = enveloped.toSchema();
const ber = content.toSchema().toBER(false);
},
error => Promise.reject(`encryption error: ${error}`)
}
//*********************************************************************************
export function decryptMessage(message, password) {
if (canTryPincode()) {
const secret = pvutils.stringToArrayBuffer(password);
const buffer = decodePEM(message);
const asn1 = asn1js.fromBER(buffer);
const content = new pkijs.ContentInfo({ schema: asn1.result });
const enveloped = new pkijs.EnvelopedData({ schema: content.content });
return enveloped
.decrypt(0, { preDefinedData: secret })
.then(result => {
return pvutils.arrayBufferToString(result);
})
.catch(() => {
return Promise.reject(failPincodeAttempt(password));
});
} else {
return Promise.reject(getTimeLeftInLocalStorage());
}
}
//*********************************************************************************
export function parsePrivateKey(privateKeyPEM) {
const privateKeyBuffer = decodePEM(privateKeyPEM);
const crypto = pkijs.getCrypto();
const privateKeyPromise = crypto.importKey(
"pkcs8",
privateKeyBuffer,
name: "RSASSA-PKCS1-v1_5",
hash: { name: "SHA-256" } //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
},
true,
["sign"]
);
return privateKeyPromise;
}
export function createPassportCertificate(commonNameArg) {
const certData = {
algorithms: {
hashAlg: "SHA-256",
signAlg: "RSASSA-PKCS1-v1_5",
keyLength: 2048
},
//keyPair: generateKeys(), //optional , if provided must be object or promise that resolves to object {publicKey, prvateKey}. If it is not provided, new ones are generated automatically
subject: {
commonName: commonNameArg + "-userdevice", //optional for leaf, recommended for CA
country: "CH", //optional for leaf, recommended for CA
locality: "Zug", //optional for leaf, recommended for CA
state: "Zug", //optional for leaf, recommended for CA
organization: "Vereign AG", //optional for leaf, recommended for CA
organizationUnit: "Business Dep" //optional for leaf, recommended for CA
//email: "damyan.mitev@vereign.com", // added to DN and Subject Alternative Name extension. Optional for CA. Mandatory for leaf certificate, used for email protection
//url: "www.vereign.com" // optional url, recommended for CA, added to Subject Alternative Name extension
},
validity: {
//notBefore: new Date() // optional, defaults to today at 00:00:00
//notAfter: new Date() // optional, defaults to notBefore + validYears at 23:59:59
validYears: 5 //optional, defaults to 1
},
isCA: true // optional flag denoting if this is CA certificate or leaf certificate, defaults to false
};
export function createOneTimePassportCertificate(
commonNameArg,
emailArg,
privateKeyIssuerArg,
certicateIssuerArg
) {
certData = {
algorithms: {
hashAlg: "SHA-256",
signAlg: "RSASSA-PKCS1-v1_5",
keyLength: 2048
},
//keyPair: generateKeys(), //optional , if provided must be object or promise that resolves to object {publicKey, prvateKey}. If it is not provided, new ones are generated automatically
subject: {
commonName: commonNameArg + "-onetime", //optional for leaf, recommended for CA
country: "CH", //optional for leaf, recommended for CA
locality: "Zug", //optional for leaf, recommended for CA
state: "Zug", //optional for leaf, recommended for CA
organization: "Vereign AG", //optional for leaf, recommended for CA
organizationUnit: "Business Dep", //optional for leaf, recommended for CA
email: emailArg // added to DN and Subject Alternative Name extension. Optional for CA. Mandatory for leaf certificate, used for email protection
//url: "www.vereign.com" // optional url, recommended for CA, added to Subject Alternative Name extension
},
validity: {
//notBefore: new Date() // optional, defaults to today at 00:00:00
//notAfter: new Date() // optional, defaults to notBefore + validYears at 23:59:59
validYears: 5 //optional, defaults to 1
},
isCA: false // optional flag denoting if this is CA certificate or leaf certificate, defaults to false
return parsePrivateKey(privateKeyIssuerArg).then(privateKeyDecoded => {
const issuerData = {
certificate: parseCertificate(certicateIssuerArg), // vereignCACertPEM),
privateKey: privateKeyDecoded
};
return createCertificate(certData, issuerData);
});
}
function arrayBufferToBase64Formatted(buffer) {
const bufferString = String.fromCharCode.apply(null, new Uint8Array(buffer));
const base64formatted = formatPEM(window.btoa(bufferString));
return base64formatted;
}
export function signEmail(mime, signingCert, certificateChain, privateKey) {
const signingCertObj = parseCertificate(signingCert);
const certificateChainObj = [];
certificateChainObj[0] = parseCertificate(signingCert);
for (let i = 0; i < certificateChain.length; i++) {
certificateChainObj[i + 1] = parseCertificate(certificateChain[i]);
}
return parsePrivateKey(privateKey).then(privateKeyDecoded => {
return signEmailObjects(
mime,
signingCertObj,
certificateChainObj,
privateKeyDecoded
);
});
}
function signEmailObjects(mime, signingCert, certificateChain, privateKey) {
//region Get a "crypto" extension
const crypto = pkijs.getCrypto();
if (typeof crypto === "undefined") {
return Promise.reject("No WebCrypto extension found");
}
//endregion Get a "crypto" extension
let template = `{{headers}}Content-Type: multipart/signed; protocol="application/pkcs7-signature"; micalg=sha-256; boundary="{{boundary}}"
MIME-Version: 1.0
This is a cryptographically signed message in MIME format.
--{{boundary}}
{{mime}}
--{{boundary}}
Content-Type: application/pkcs7-signature; name="smime.p7s"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smime.p7s"
Content-Description: S/MIME Cryptographic Signature
{{signature}}
--{{boundary}}--
Vereign - Authentic Communication
const detachedSignature = true;
const addExt = true;
const hashAlg = "SHA-256";
let cmsSignedSimpl;
"Content-Type",
"Content-Transfer-Encoding",
"Content-ID",
"Content-Description",
"Content-Disposition",
"Content-Language",
"Content-Location"
];
let newHeaderLines = "";
let headersEnd = mime.indexOf("\r\n\r\n"); //the first empty line
if (headersEnd < 0 && mime.startsWith("\r\n")) {
mime = mime.substring(2); //should not happen
} else if (headersEnd >= 0) {
let mimeHeaders = {};
let mimeBody = mime.substring(headersEnd + 4);
let mimeHeadersStr = mime.substring(0, headersEnd);
let headers = libmime.decodeHeaders(mimeHeadersStr);
for (let i = 0; i < mimeHeadersTitles.length; i++) {
let key = mimeHeadersTitles[i].toLowerCase();
mimeHeaders[key] = headers[key];
for (let key in headers) {
if (!(key === "" || key === "MIME-Version".toLowerCase())) {
//we have MIME-Version in the template
newHeaderLines += capitalizeHeader(key) + ": " + headers[key] + "\r\n";
}
}
let newMimeHeaderLines = "";
for (let key in mimeHeaders) {
if (!(key === "")) {
newMimeHeaderLines +=
capitalizeHeader(key) + ": " + mimeHeaders[key] + "\r\n";
}
}
if (newMimeHeaderLines === "") {
newMimeHeaderLines = "Content-Type: text/plain\r\n"; //should not happen
let sequence = Promise.resolve();
//region Check if user wants us to include signed extensions
//region Create a message digest
sequence = sequence.then(() =>
crypto.digest({ name: hashAlg }, dataBuffer)
);
//endregion
//region Combine all signed extensions
sequence = sequence.then(messageHash => {
const signedAttr = [];
/*
1.2.840.113549.1.9.1 - e-mailAddress
1.2.840.113549.1.9.2 - PKCS-9 unstructuredName
1.2.840.113549.1.9.3 - contentType
1.2.840.113549.1.9.4 - messageDigest
1.2.840.113549.1.9.5 - Signing Time
1.2.840.113549.1.9.6 - counterSignature
*/
type: OID_PKCS9_ContentType, //contentType
values: [
]
/*
1.2.840.113549.1.7.1 - data
1.2.840.113549.1.7.2 - signedData
1.2.840.113549.1.7.3 - envelopedData
1.2.840.113549.1.7.4 - signedAndEnvelopedData
1.2.840.113549.1.7.5 - digestedData
1.2.840.113549.1.7.6 - encryptedData
*/
type: OID_PKCS9_SigningTime, //Signing Time
values: [new asn1js.UTCTime({ valueDate: new Date() })]
})
); // signingTime
type: OID_PKCS9_MessageDigest, //messageDigest
values: [new asn1js.OctetString({ valueHex: messageHash })]
})
); // messageDigest
//endregion
}
//endregion
//region Initialize CMS Signed Data structures and sign it
sequence = sequence.then(signedAttr => {
cmsSignedSimpl = new pkijs.SignedData({
version: 1,
encapContentInfo: new pkijs.EncapsulatedContentInfo({
eContentType: OID_PKCS7_Data // "data" content type
}),
signerInfos: [
new pkijs.SignerInfo({
version: 1,
sid: new pkijs.IssuerAndSerialNumber({
issuer: signingCert.issuer,
serialNumber: signingCert.serialNumber
if (addExt) {
cmsSignedSimpl.signerInfos[0].signedAttrs = new pkijs.SignedAndUnsignedAttributes(
{
type: 0,
attributes: signedAttr
if (detachedSignature === false) {
const contentInfo = new pkijs.EncapsulatedContentInfo({
eContent: new asn1js.OctetString({ valueHex: dataBuffer })
});
cmsSignedSimpl.encapContentInfo.eContent = contentInfo.eContent;
return cmsSignedSimpl.sign(privateKey, 0, hashAlg, dataBuffer);
});
//endregion
//region Create final result
sequence = sequence.then(
const cmsSignedSchema = cmsSignedSimpl.toSchema(true);
const cmsContentSimp = new pkijs.ContentInfo({
contentType: OID_PKCS7_SignedData, //signedData
content: cmsSignedSchema
});
const _cmsSignedSchema = cmsContentSimp.toSchema();
//region Make length of some elements in "indefinite form"
_cmsSignedSchema.lenBlock.isIndefiniteForm = true;
const block1 = _cmsSignedSchema.valueBlock.value[1];
block1.lenBlock.isIndefiniteForm = true;
const block2 = block1.valueBlock.value[0];
block2.lenBlock.isIndefiniteForm = true;
const block3 = block2.valueBlock.value[2];
block3.lenBlock.isIndefiniteForm = true;
block3.valueBlock.value[1].lenBlock.isIndefiniteForm = true;
block3.valueBlock.value[1].valueBlock.value[0].lenBlock.isIndefiniteForm = true;
}
//endregion
const cmsSignedBuffer = _cmsSignedSchema.toBER(false);
return cmsSignedBuffer;
},
error => Promise.reject(`Erorr during signing of CMS Signed Data: ${error}`)
);
//endregion