export interface IEnvelopedData { /** * Version number. * * The appropriate value depends on `originatorInfo`, `RecipientInfo`, and `unprotectedAttrs`. * * The version MUST be assigned as follows: * ``` * IF (originatorInfo is present) AND * ((any certificates with a type of other are present) OR * (any crls with a type of other are present)) * THEN version is 4 * ELSE * IF ((originatorInfo is present) AND * (any version 2 attribute certificates are present)) OR * (any RecipientInfo structures include pwri) OR * (any RecipientInfo structures include ori) * THEN version is 3 * ELSE * IF (originatorInfo is absent) AND * (unprotectedAttrs is absent) AND * (all RecipientInfo structures are version 0) * THEN version is 0 * ELSE version is 2 * ```
*/
version: number; /** * Optionally provides information about the originator. It is present only if required by the key management algorithm. * It may contain certificates and CRLs.
*/
originatorInfo?: OriginatorInfo; /** * Collection of per-recipient information. There MUST be at least one element in the collection.
*/
recipientInfos: RecipientInfo[]; /** * Encrypted content information
*/
encryptedContentInfo: EncryptedContentInfo; /** * Collection of attributes that are not encrypted
*/
unprotectedAttrs?: Attribute[];
}
public version!: number; public originatorInfo?: OriginatorInfo; public recipientInfos!: RecipientInfo[]; public encryptedContentInfo!: EncryptedContentInfo; public unprotectedAttrs?: Attribute[];
/** * Initializes a new instance of the {@link EnvelopedData} class * @param parameters Initialization parameters
*/
constructor(parameters: EnvelopedDataParameters = {}) { super();
this.version = pvutils.getParametersValue(parameters, VERSION, EnvelopedData.defaultValues(VERSION)); if (ORIGINATOR_INFO in parameters) { this.originatorInfo = pvutils.getParametersValue(parameters, ORIGINATOR_INFO, EnvelopedData.defaultValues(ORIGINATOR_INFO));
} this.recipientInfos = pvutils.getParametersValue(parameters, RECIPIENT_INFOS, EnvelopedData.defaultValues(RECIPIENT_INFOS)); this.encryptedContentInfo = pvutils.getParametersValue(parameters, ENCRYPTED_CONTENT_INFO, EnvelopedData.defaultValues(ENCRYPTED_CONTENT_INFO)); if (UNPROTECTED_ATTRS in parameters) { this.unprotectedAttrs = pvutils.getParametersValue(parameters, UNPROTECTED_ATTRS, EnvelopedData.defaultValues(UNPROTECTED_ATTRS));
}
if (parameters.schema) { this.fromSchema(parameters.schema);
}
}
/** * Returns default values for all class members * @param memberName String name for a class member * @returns Default value
*/ publicstatic override defaultValues(memberName: typeof VERSION): number; publicstatic override defaultValues(memberName: typeof ORIGINATOR_INFO): OriginatorInfo; publicstatic override defaultValues(memberName: typeof RECIPIENT_INFOS): RecipientInfo[]; publicstatic override defaultValues(memberName: typeof ENCRYPTED_CONTENT_INFO): EncryptedContentInfo; publicstatic override defaultValues(memberName: typeof UNPROTECTED_ATTRS): Attribute[]; publicstatic override defaultValues(memberName: string): any { switch (memberName) { case VERSION: return0; case ORIGINATOR_INFO: returnnew OriginatorInfo(); case RECIPIENT_INFOS: return []; case ENCRYPTED_CONTENT_INFO: returnnew EncryptedContentInfo(); case UNPROTECTED_ATTRS: return []; default: returnsuper.defaultValues(memberName);
}
}
/** * Compare values with default values for all class members * @param memberName String name for a class member * @param memberValue Value to compare with default value
*/ publicstatic compareWithDefault(memberName: string, memberValue: any): boolean { switch (memberName) { case VERSION: return (memberValue === EnvelopedData.defaultValues(memberName)); case ORIGINATOR_INFO: return ((memberValue.certs.certificates.length === 0) && (memberValue.crls.crls.length === 0)); case RECIPIENT_INFOS: case UNPROTECTED_ATTRS: return (memberValue.length === 0); case ENCRYPTED_CONTENT_INFO: return ((EncryptedContentInfo.compareWithDefault("contentType", memberValue.contentType)) &&
(EncryptedContentInfo.compareWithDefault("contentEncryptionAlgorithm", memberValue.contentEncryptionAlgorithm) &&
(EncryptedContentInfo.compareWithDefault("encryptedContent", memberValue.encryptedContent)))); default: returnsuper.defaultValues(memberName);
}
}
// Get internal properties from parsed schema this.version = asn1.result.version.valueBlock.valueDec;
if (ORIGINATOR_INFO in asn1.result) { this.originatorInfo = new OriginatorInfo({
schema: new asn1js.Sequence({
value: asn1.result.originatorInfo.valueBlock.value
})
});
}
this.recipientInfos = Array.from(asn1.result.recipientInfos, o => new RecipientInfo({ schema: o })); this.encryptedContentInfo = new EncryptedContentInfo({ schema: asn1.result.encryptedContentInfo });
if (UNPROTECTED_ATTRS in asn1.result) this.unprotectedAttrs = Array.from(asn1.result.unprotectedAttrs, o => new Attribute({ schema: o }));
}
public toSchema(): asn1js.Sequence { //#region Create array for output sequence const outputArray = [];
//#region Construct and return new ASN.1 schema for this object return (new asn1js.Sequence({
value: outputArray
})); //#endregion
}
public toJSON(): EnvelopedDataJson { const res: EnvelopedDataJson = {
version: this.version,
recipientInfos: Array.from(this.recipientInfos, o => o.toJSON()),
encryptedContentInfo: this.encryptedContentInfo.toJSON(),
};
if (this.originatorInfo)
res.originatorInfo = this.originatorInfo.toJSON();
if (this.unprotectedAttrs)
res.unprotectedAttrs = Array.from(this.unprotectedAttrs, o => o.toJSON());
return res;
}
/** * Helpers function for filling "RecipientInfo" based on recipient's certificate. * Problem with WebCrypto is that for RSA certificates we have only one option - "key transport" and * for ECC certificates we also have one option - "key agreement". As soon as Google will implement * DH algorithm it would be possible to use "key agreement" also for RSA certificates. * @param certificate Recipient's certificate * @param parameters Additional parameters necessary for "fine tunning" of encryption process * @param variant Variant = 1 is for "key transport", variant = 2 is for "key agreement". In fact the "variant" is unnecessary now because Google has no DH algorithm implementation. Thus key encryption scheme would be choosen by certificate type only: "key transport" for RSA and "key agreement" for ECC certificates. * @param crypto Crypto engine
*/ public addRecipientByCertificate(certificate: Certificate, parameters?: { // empty
}, variant?: number, crypto = common.getCrypto(true)): boolean { //#region Initialize encryption parameters const encryptionParameters = Object.assign(
{ useOAEP: true, oaepHashAlgorithm: "SHA-512" },
defaultEncryptionParams,
parameters || {}
); //#endregion
//#region Check type of certificate if (certificate.subjectPublicKeyInfo.algorithm.algorithmId.indexOf("1.2.840.113549") !== (-1))
variant = 1; // For the moment it is the only variant for RSA-based certificates else { if (certificate.subjectPublicKeyInfo.algorithm.algorithmId.indexOf("1.2.840.10045") !== (-1))
variant = 2; // For the moment it is the only variant for ECC-based certificates else thrownew Error(`Unknown type of certificate's public key: ${certificate.subjectPublicKeyInfo.algorithm.algorithmId}`);
} //#endregion
//#region Add new "recipient" depends on "variant" and certificate type switch (variant) { case1: // Key transport scheme
{
let algorithmId;
let algorithmParams;
const hashAlgorithm = new AlgorithmIdentifier({
algorithmId: hashOID,
algorithmParams: new asn1js.Null()
});
const rsaOAEPParams = new RSAESOAEPParams({
hashAlgorithm,
maskGenAlgorithm: new AlgorithmIdentifier({
algorithmId: "1.2.840.113549.1.1.8", // id-mgf1
algorithmParams: hashAlgorithm.toSchema()
})
});
algorithmParams = rsaOAEPParams.toSchema(); //#endregion
} else// Use old RSAES-PKCS1-v1_5 schema instead
{ //#region keyEncryptionAlgorithm
algorithmId = crypto.getOIDByAlgorithm({
name: "RSAES-PKCS1-v1_5"
}); if (algorithmId === EMPTY_STRING) thrownew Error("Can not find OID for RSAES-PKCS1-v1_5"); //#endregion
algorithmParams = new asn1js.Null();
}
//#region KeyTransRecipientInfo const keyInfo = new KeyTransRecipientInfo({
version: 0,
rid: new IssuerAndSerialNumber({
issuer: certificate.issuer,
serialNumber: certificate.serialNumber
}),
keyEncryptionAlgorithm: new AlgorithmIdentifier({
algorithmId,
algorithmParams
}),
recipientCertificate: certificate, // "encryptedKey" will be calculated in "encrypt" function
}); //#endregion
if (!parameters.keyEncryptionAlgorithmParams)
parameters.keyEncryptionAlgorithmParams = new asn1js.Null(); //#endregion
//#region Add new recipient based on passed variant switch (variant) { case1: // KEKRecipientInfo
{ // keyEncryptionAlgorithm const kekOID = crypto.getOIDByAlgorithm(parameters.keyEncryptionAlgorithm, true, "keyEncryptionAlgorithm");
//#region KEKRecipientInfo const keyInfo = new KEKRecipientInfo({
version: 4,
kekid: new KEKIdentifier({
keyIdentifier: new asn1js.OctetString({ valueHex: parameters.keyIdentifier })
}),
keyEncryptionAlgorithm: new AlgorithmIdentifier({
algorithmId: kekOID, /* For AES-KW params are NULL, but for other algorithm could another situation.
*/
algorithmParams: parameters.keyEncryptionAlgorithmParams
}),
preDefinedKEK: preDefinedData // "encryptedKey" would be set in "ecrypt" function
}); //#endregion
//#region PasswordRecipientinfo const keyInfo = new PasswordRecipientinfo({
version: 0,
keyDerivationAlgorithm: new AlgorithmIdentifier({
algorithmId: pbkdf2OID,
algorithmParams: pbkdf2Params.toSchema()
}),
keyEncryptionAlgorithm: new AlgorithmIdentifier({
algorithmId: kekOID, /* For AES-KW params are NULL, but for other algorithm could be another situation.
*/
algorithmParams: parameters.keyEncryptionAlgorithmParams
}),
password: preDefinedData // "encryptedKey" would be set in "encrypt" function
}); //#endregion
//#region Final values for "CMS_ENVELOPED_DATA" this.recipientInfos.push(new RecipientInfo({
variant: 4,
value: keyInfo
})); //#endregion
} break; default: thrownew Error(`Unknown value for"variant": ${variant}`);
} //#endregion
}
/** * Add a "RecipientInfo" using a KeyAgreeRecipientInfo of type RecipientKeyIdentifier. * @param key Recipient's public key * @param keyId The id for the recipient's public key * @param parameters Additional parameters for "fine tuning" the encryption process * @param crypto Crypto engine
*/
addRecipientByKeyIdentifier(key?: CryptoKey, keyId?: ArrayBuffer, parameters?: any, crypto = common.getCrypto(true)) { //#region Initialize encryption parameters const encryptionParameters = Object.assign({}, defaultEncryptionParams, parameters || {}); //#endregion
const recipientIdentifier = new KeyAgreeRecipientIdentifier({
variant: 2,
value: new RecipientKeyIdentifier({
subjectKeyIdentifier: new asn1js.OctetString({ valueHex: keyId }),
})
}); this._addKeyAgreeRecipientInfo(
recipientIdentifier,
encryptionParameters,
{ recipientPublicKey: key },
crypto,
);
}
/** * Add a "RecipientInfo" using a KeyAgreeRecipientInfo of type RecipientKeyIdentifier. * @param recipientIdentifier Recipient identifier * @param encryptionParameters Additional parameters for "fine tuning" the encryption process * @param extraRecipientInfoParams Additional params for KeyAgreeRecipientInfo * @param crypto Crypto engine
*/ private _addKeyAgreeRecipientInfo(recipientIdentifier: KeyAgreeRecipientIdentifier, encryptionParameters: EnvelopedDataEncryptionParams, extraRecipientInfoParams: KeyAgreeRecipientInfoParameters, crypto = common.getCrypto(true)) { //#region RecipientEncryptedKey const encryptedKey = new RecipientEncryptedKey({
rid: recipientIdentifier // "encryptedKey" will be calculated in "encrypt" function
}); //#endregion
// In fact there is no need in so long UKM, but RFC2631 // has requirement that "UserKeyMaterial" must be 512 bits long const ukmBuffer = new ArrayBuffer(64); const ukmView = new Uint8Array(ukmBuffer);
crypto.getRandomValues(ukmView); // Generate random values in 64 bytes long buffer
const recipientInfoParams = {
version: 3, // "originator" will be calculated in "encrypt" function because ephemeral key would be generated there
ukm: new asn1js.OctetString({ valueHex: ukmBuffer }),
keyEncryptionAlgorithm: new AlgorithmIdentifier({
algorithmId: ecdhOID,
algorithmParams: aesKW.toSchema()
}),
recipientEncryptedKeys: new RecipientEncryptedKeys({
encryptedKeys: [encryptedKey]
})
}; const keyInfo = new KeyAgreeRecipientInfo(Object.assign(recipientInfoParams, extraRecipientInfoParams)); //#endregion
//#region Final values for "CMS_ENVELOPED_DATA" this.recipientInfos.push(new RecipientInfo({
variant: 2,
value: keyInfo
})); //#endregion
}
/** * Creates a new CMS Enveloped Data content with encrypted data * @param contentEncryptionAlgorithm WebCrypto algorithm. For the moment here could be only "AES-CBC" or "AES-GCM" algorithms. * @param contentToEncrypt Content to encrypt * @param crypto Crypto engine
*/ public async encrypt(contentEncryptionAlgorithm: Algorithm, contentToEncrypt: ArrayBuffer, crypto = common.getCrypto(true)): Promise<(void | { ecdhPrivateKey: CryptoKey; })[]> { //#region Initial variables const ivBuffer = new ArrayBuffer(16); // For AES we need IV 16 bytes long const ivView = new Uint8Array(ivBuffer);
crypto.getRandomValues(ivView);
const contentView = new Uint8Array(contentToEncrypt); //#endregion
//#endregion //#region Append common information to CMS_ENVELOPED_DATA this.version = 2; this.encryptedContentInfo = new EncryptedContentInfo({
contentType: "1.2.840.113549.1.7.1", // "data"
contentEncryptionAlgorithm: new AlgorithmIdentifier({
algorithmId: contentEncryptionOID,
algorithmParams: new asn1js.OctetString({ valueHex: ivBuffer })
}),
encryptedContent: new asn1js.OctetString({ valueHex: encryptedContent })
}); //#endregion
//#region Special sub-functions to work with each recipient's type const SubKeyAgreeRecipientInfo = async (index: number) => { //#region Initial variables const recipientInfo = this.recipientInfos[index].value as KeyAgreeRecipientInfo;
let recipientCurve: string; //#endregion
//#region Get public key and named curve from recipient's certificate or public key
let recipientPublicKey: CryptoKey; if (recipientInfo.recipientPublicKey) {
recipientCurve = (recipientInfo.recipientPublicKey.algorithm as EcKeyAlgorithm).namedCurve;
recipientPublicKey = recipientInfo.recipientPublicKey;
} elseif (recipientInfo.recipientCertificate) { const curveObject = recipientInfo.recipientCertificate.subjectPublicKeyInfo.algorithm.algorithmParams;
if (curveObject.constructor.blockName() !== asn1js.ObjectIdentifier.blockName()) thrownew Error(`Incorrect "recipientCertificate"for index ${index}`);
//#region Get length of used AES-KW algorithm const aesKWAlgorithm = new AlgorithmIdentifier({ schema: recipientInfo.keyEncryptionAlgorithm.algorithmParams });
//#region Get SHA algorithm used together with ECDH const ecdhAlgorithm = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "ecdhAlgorithm"); //#endregion
const derivedKeyRaw = await common.kdf(ecdhAlgorithm.kdf, derivedBits, kwAlgorithm.length, encodedInfo, crypto); //#endregion //#region Import AES-KW key from result of KDF function const awsKW = await crypto.importKey("raw", derivedKeyRaw, { name: "AES-KW" }, true, ["wrapKey"]); //#endregion //#region Finally wrap session key by using AES-KW algorithm const wrappedKey = await crypto.wrapKey("raw", sessionKey, awsKW, { name: "AES-KW" }); //#endregion //#region Append all necessary data to current CMS_RECIPIENT_INFO object //#region OriginatorIdentifierOrKey const originator = new OriginatorIdentifierOrKey();
originator.variant = 3;
originator.value = OriginatorPublicKey.fromBER(exportedECDHPublicKey);
//#region RecipientEncryptedKey /* We will not support using of same ephemeral key for many recipients
*/
recipientInfo.recipientEncryptedKeys.encryptedKeys[0].encryptedKey = new asn1js.OctetString({ valueHex: wrappedKey }); //#endregion
//#region RSA-OAEP case if (algorithmParameters.name === "RSA-OAEP") { const schema = recipientInfo.keyEncryptionAlgorithm.algorithmParams; const rsaOAEPParams = new RSAESOAEPParams({ schema });
algorithmParameters.hash = crypto.getAlgorithmByOID(rsaOAEPParams.hashAlgorithm.algorithmId); if (("name" in algorithmParameters.hash) === false) thrownew Error(`Incorrect OID for hash algorithm: ${rsaOAEPParams.hashAlgorithm.algorithmId}`);
} //#endregion
//#region Get WebCrypto form of "keyEncryptionAlgorithm" const kekAlgorithm = crypto.getAlgorithmByOID(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "kekAlgorithm"); //#endregion
const kekKey = await crypto.importKey("raw", new Uint8Array(recipientInfo.preDefinedKEK),
kekAlgorithm, true,
["wrapKey"]); // Too specific for AES-KW //#endregion
//#region Wrap previously exported session key
const wrappedKey = await crypto.wrapKey("raw", sessionKey, kekKey, kekAlgorithm); //#endregion //#region Append all necessary data to current CMS_RECIPIENT_INFO object //#region RecipientEncryptedKey
recipientInfo.encryptedKey = new asn1js.OctetString({ valueHex: wrappedKey }); //#endregion //#endregion
};
const SubPasswordRecipientinfo = async (index: number) => { //#region Initial variables const recipientInfo = this.recipientInfos[index].value as PasswordRecipientinfo; // TODO Remove `as PasswordRecipientinfo`
let pbkdf2Params: PBKDF2Params; //#endregion
//#region Check that we have encoded "keyDerivationAlgorithm" plus "PBKDF2_params" in there
if (!recipientInfo.keyDerivationAlgorithm) thrownew Error("Please append encoded \"keyDerivationAlgorithm\"");
if (!recipientInfo.keyDerivationAlgorithm.algorithmParams) thrownew Error("Incorrectly encoded \"keyDerivationAlgorithm\"");
//#region Check for input parameters if ((recipientIndex + 1) > this.recipientInfos.length) { thrownew Error(`Maximum value for"index" is: ${this.recipientInfos.length - 1}`);
} //#endregion
//#region Special sub-functions to work with each recipient's type const SubKeyAgreeRecipientInfo = async (index: number) => { //#region Initial variables const recipientInfo = this.recipientInfos[index].value as KeyAgreeRecipientInfo; // TODO Remove `as KeyAgreeRecipientInfo` //#endregion
let curveOID: string;
let recipientCurve: string;
let recipientCurveLength: number; const originator = recipientInfo.originator;
//#region Get "namedCurve" parameter from recipient's certificate
if (decryptionParameters.recipientCertificate) { const curveObject = decryptionParameters.recipientCertificate.subjectPublicKeyInfo.algorithm.algorithmParams; if (curveObject.constructor.blockName() !== asn1js.ObjectIdentifier.blockName()) { thrownew Error(`Incorrect "recipientCertificate"for index ${index}`);
}
curveOID = curveObject.valueBlock.toString();
} elseif (originator.value.algorithm.algorithmParams) { const curveObject = originator.value.algorithm.algorithmParams; if (curveObject.constructor.blockName() !== asn1js.ObjectIdentifier.blockName()) { thrownew Error(`Incorrect originator for index ${index}`);
}
curveOID = curveObject.valueBlock.toString();
} else { thrownew Error("Parameter \"recipientCertificate\" is mandatory for \"KeyAgreeRecipientInfo\" if algorithm params are missing from originator");
}
if (!decryptionParameters.recipientPrivateKey) thrownew Error("Parameter \"recipientPrivateKey\" is mandatory for \"KeyAgreeRecipientInfo\"");
//#region Get length of used AES-KW algorithm const aesKWAlgorithm = new AlgorithmIdentifier({ schema: recipientInfo.keyEncryptionAlgorithm.algorithmParams });
//#region Get SHA algorithm used together with ECDH const ecdhAlgorithm = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "ecdhAlgorithm"); if (!ecdhAlgorithm.name) { thrownew Error(`Incorrect OID for key encryption algorithm: ${recipientInfo.keyEncryptionAlgorithm.algorithmId}`);
} //#endregion
//#region RSA-OAEP case if (algorithmParameters.name === "RSA-OAEP") { const schema = recipientInfo.keyEncryptionAlgorithm.algorithmParams; const rsaOAEPParams = new RSAESOAEPParams({ schema });
algorithmParameters.hash = crypto.getAlgorithmByOID(rsaOAEPParams.hashAlgorithm.algorithmId); if (("name" in algorithmParameters.hash) === false) thrownew Error(`Incorrect OID for hash algorithm: ${rsaOAEPParams.hashAlgorithm.algorithmId}`);
} //#endregion
//#region Import KEK from pre-defined data if (!decryptionParameters.preDefinedData) thrownew Error("Parameter \"preDefinedData\" is mandatory for \"KEKRecipientInfo\"");
//#region Get WebCrypto form of "keyEncryptionAlgorithm" const kekAlgorithm = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "kekAlgorithm"); //#endregion
const importedKey = await crypto.importKey("raw",
decryptionParameters.preDefinedData,
kekAlgorithm, true,
["unwrapKey"]); // Too specific for AES-KW
//#endregion //#region Unwrap previously exported session key //#region Get WebCrypto form of content encryption algorithm const algorithmId = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId; const contentEncryptionAlgorithm = crypto.getAlgorithmByOID<any>(algorithmId, true, "contentEncryptionAlgorithm"); if (!contentEncryptionAlgorithm.name) { thrownew Error(`Incorrect "contentEncryptionAlgorithm": ${algorithmId}`);
} //#endregion
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.