Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement and .NET interoperability #1084

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,38 @@
Forge ChangeLog
===============

## 1.3.3 - 2024-06-05
### Enhancement and interoperability
- **At [pkcs12.js] and [pbe.js].**
- Fix in password mechanism.

## 1.3.2 - 2024-06-04

### Enhancement and interoperability
- **At [pkcs12.js] and [pbe.js].**
- Added (options.encryptCert) flag to allow encrypt [PKCS#7 ContentInfo], used in [PKCS#12 toPkcs12Asn1].
- This ensures interoperability with [System.Security.Cryptography.X509Certificates.X509Certificate2(cert, password)].
- Added suport for .NET interoperability for X509Certificate2(cert, password = "")
for do this set toPkcs12Asn1 with password = undefined.
### Completing OIDs
- **At [oids.js] and [x509.js].**
- Was added this: **'ocsp','caIssuers'** OIDs.
- Added setExtensions for this OIDs: 'ocsp','caIssuers'.
```js
//Example of use:
{
name: 'authorityInfoAccess', //OCSP is Implemented in Fork https://github.com/SevanMelemedjian/forge.git
accessDescriptions: [
{
accessMethod: 'ocsp',
accessLocation: 'http://yoursite.com/static/ocsp'
},{
accessMethod: 'caIssuers',
accessLocation: 'http://yoursite.com/static/root.crt'
}]
}
```

## 1.3.1 - 2022-03-29

### Fixes
Expand Down
4 changes: 4 additions & 0 deletions lib/oids.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,7 @@ _IN('1.3.6.1.5.5.7.3.2', 'clientAuth');
_IN('1.3.6.1.5.5.7.3.3', 'codeSigning');
_IN('1.3.6.1.5.5.7.3.4', 'emailProtection');
_IN('1.3.6.1.5.5.7.3.8', 'timeStamping');

// authorityInfoAccess methods OIDs
_IN('1.3.6.1.5.5.7.48.1', 'ocsp');
_IN('1.3.6.1.5.5.7.48.2', 'caIssuers');
154 changes: 153 additions & 1 deletion lib/pbe.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ pki.encryptPrivateKeyInfo = function(obj, password, options) {
var md = prfAlgorithmToMessageDigest(prfAlgorithm);

// encrypt private key using pbe SHA-1 and AES/DES
var dk = forge.pkcs5.pbkdf2(password, salt, count, dkLen, md);
var dk = forge.pkcs5.pbkdf2((password === undefined)? "": password, salt, count, dkLen, md);
var iv = forge.random.getBytesSync(ivLen);
var cipher = cipherFn(dk);
cipher.start(iv);
Expand Down Expand Up @@ -345,6 +345,157 @@ pki.encryptPrivateKeyInfo = function(obj, password, options) {
]);
return rval;
};
//----MOD BY SEBA-----------------------------------------------------------------------
/*
@param obj the ASN.1 ContentInfo object.
* @param password the password to encrypt with.
* @param options:
* algorithm the encryption algorithm to use
* ('aes128', 'aes192', 'aes256', '3des'), defaults to 'aes128'.
* count the iteration count to use.
* saltSize the salt size to use.
* prfAlgorithm the PRF message digest algorithm to use
* ('sha1', 'sha224', 'sha256', 'sha384', 'sha512')
*
* @return the ASN.1 EncryptedContentInfo.
* */
pki.encryptContentInfo = function(obj, password, options) {
// set default options
options = options || {};
options.saltSize = options.saltSize || 8;
options.count = options.count || 2048;
options.algorithm = options.algorithm || 'aes128';
options.prfAlgorithm = options.prfAlgorithm || 'sha1';

// generate PBE params
var salt = forge.random.getBytesSync(options.saltSize);
var count = options.count;
var countBytes = asn1.integerToDer(count);
var dkLen;
var encryptionAlgorithm;
var encryptedData;
if(options.algorithm.indexOf('aes') === 0 || options.algorithm === 'des') {
// do PBES2
var ivLen, encOid, cipherFn;
switch(options.algorithm) {
case 'aes128':
dkLen = 16;
ivLen = 16;
encOid = oids['aes128-CBC'];
cipherFn = forge.aes.createEncryptionCipher;
break;
case 'aes192':
dkLen = 24;
ivLen = 16;
encOid = oids['aes192-CBC'];
cipherFn = forge.aes.createEncryptionCipher;
break;
case 'aes256':
dkLen = 32;
ivLen = 16;
encOid = oids['aes256-CBC'];
cipherFn = forge.aes.createEncryptionCipher;
break;
case 'des':
dkLen = 8;
ivLen = 8;
encOid = oids['desCBC'];
cipherFn = forge.des.createEncryptionCipher;
break;
default:
var error = new Error('Cannot encrypt Content Info. Unknown encryption algorithm.');
error.algorithm = options.algorithm;
throw error;
}

// get PRF message digest
var prfAlgorithm = 'hmacWith' + options.prfAlgorithm.toUpperCase();
var md = prfAlgorithmToMessageDigest(prfAlgorithm);

// encrypt ContentInfo using pbe SHA-1 and AES/DES
var dk = forge.pkcs5.pbkdf2((password === undefined)? "": password, salt, count, dkLen, md);
var iv = forge.random.getBytesSync(ivLen);
var cipher = cipherFn(dk);
cipher.start(iv);
cipher.update(asn1.toDer(obj));
cipher.finish();
encryptedData = cipher.output.getBytes();

// get PBKDF2-params
var params = createPbkdf2Params(salt, countBytes, dkLen, prfAlgorithm);

encryptionAlgorithm = asn1.create(
asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
asn1.oidToDer(oids['pkcs5PBES2']).getBytes()),
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
// keyDerivationFunc
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
asn1.oidToDer(oids['pkcs5PBKDF2']).getBytes()),
// PBKDF2-params
params
]),
// encryptionScheme
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
asn1.oidToDer(encOid).getBytes()),
// iv
asn1.create(
asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, iv)
])
])
]);

} else if(options.algorithm === '3des') {
// Do PKCS12 PBE
dkLen = 24;

var saltBytes = new forge.util.ByteBuffer(salt);
var dk = pki.pbe.generatePkcs12Key(password, saltBytes, 1, count, dkLen);
var iv = pki.pbe.generatePkcs12Key(password, saltBytes, 2, count, dkLen);
var cipher = forge.des.createEncryptionCipher(dk);
cipher.start(iv);
cipher.update(asn1.toDer(obj));
cipher.finish();
encryptedData = cipher.output.getBytes();

encryptionAlgorithm = asn1.create(
asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
asn1.oidToDer(oids['pbeWithSHAAnd3-KeyTripleDES-CBC']).getBytes()),
// pkcs-12PbeParams
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
// salt
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, salt),
// iteration count
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
countBytes.getBytes())
])
]);

} else {
var error = new Error('Cannot encrypt Content Info. Unknown encryption algorithm.');
error.algorithm = options.algorithm;
throw error;
}
const rval = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
// Version
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false, asn1.integerToDer(0).getBytes()),
//EncryptedContentInfo
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
// contentType
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, asn1.oidToDer(pki.oids.data).getBytes()),
// contentEncryptionAlgorithm
encryptionAlgorithm,
// encryptedContent
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, encryptedData) // Encrypted content
]),
])
]);
return rval;
};

/**
* Decrypts a ASN.1 PrivateKeyInfo object.
Expand All @@ -369,6 +520,7 @@ pki.decryptPrivateKeyInfo = function(obj, password) {

// get cipher
var oid = asn1.derToOid(capture.encryptionOid);
if (oid !== pki.oids['pbeWithSHAAnd3-KeyTripleDES-CBC'] && password === undefined) password = "";
var cipher = pki.pbe.getCipher(oid, capture.encryptionParams, password);

// get encrypted data
Expand Down
61 changes: 42 additions & 19 deletions lib/pkcs12.js
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,7 @@ function _decryptSafeContents(data, password) {

// get cipher
oid = asn1.derToOid(capture.encAlgorithm);
if (oid !== pki.oids['pbeWithSHAAnd3-KeyTripleDES-CBC'] && password === undefined) password = "";
var cipher = pki.pbe.getCipher(oid, capture.encParameter, password);

// get encrypted data
Expand Down Expand Up @@ -790,7 +791,7 @@ function _decodeBagAttributes(attributes) {
* friendlyName the friendly name to use.
* generateLocalKeyId true to generate a random local key ID,
* false not to, defaults to true.
*
* encryptCert true encrypt certSafeContents with password. //Please Note: Added for interoperability with (.NET Framework) -> System.Security.Cryptography.X509Certificates.X509Certificate2(cert, "password");
* @return the PKCS#12 PFX ASN.1 object.
*/
p12.toPkcs12Asn1 = function(key, cert, password, options) {
Expand All @@ -799,6 +800,10 @@ p12.toPkcs12Asn1 = function(key, cert, password, options) {
options.saltSize = options.saltSize || 8;
options.count = options.count || 2048;
options.algorithm = options.algorithm || options.encAlgorithm || 'aes128';
options.encryptCert = options.encryptCert || false;
if(!('encryptCert' in options)) {
options.encryptCert = false;
}
if(!('useMac' in options)) {
options.useMac = true;
}
Expand Down Expand Up @@ -918,24 +923,43 @@ p12.toPkcs12Asn1 = function(key, cert, password, options) {
var certSafeContents = asn1.create(
asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, certSafeBags);

// ContentInfo
var certCI =
// PKCS#7 ContentInfo
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
// contentType
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
// OID for the content type is 'data'
asn1.oidToDer(pki.oids.data).getBytes()),
// content
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
asn1.create(
asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
asn1.toDer(certSafeContents).getBytes())
])
]);
if(!options.encryptCert) {
// ContentInfo
var certCI =
// PKCS#7 ContentInfo
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
// contentType
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
// OID for the content type is 'data'
asn1.oidToDer(pki.oids.data).getBytes()),
// content
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
asn1.create(
asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
asn1.toDer(certSafeContents).getBytes())
])
]);
}else if (password !== null) {
//Encrypted ContentInfo ContentInfo - MOD BY SEBA
var certCI =
// PKCS#7 ContentInfo
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
// contentType
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
// OID for the content type is 'encryptedData'
asn1.oidToDer(pki.oids.encryptedData).getBytes()),
// content
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
pki.encryptContentInfo(certSafeContents, password, options)
])
]);
} else {
var error = new Error('Cannot encrypt Content Info. Needs a non empty password.');
error.encryptCert = options.encryptCert;
throw error;
}
contents.push(certCI);
}

}
// create safe contents for private key
var keyBag = null;
if(key !== null) {
Expand Down Expand Up @@ -970,7 +994,6 @@ p12.toPkcs12Asn1 = function(key, cert, password, options) {
bagAttrs
]);
}

// SafeContents
var keySafeContents =
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [keyBag]);
Expand Down
34 changes: 34 additions & 0 deletions lib/x509.js
Original file line number Diff line number Diff line change
Expand Up @@ -2231,6 +2231,40 @@ function _fillMissingExtensionFields(e, options) {
seq.push(
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, false, serialNumber));
}
} else if(e.name === 'authorityInfoAccess') {
e.value = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
var accessDescription;

for(var n = 0; n < e.accessDescriptions.length; ++n) {
accessDescription = e.accessDescriptions[n];
var value =
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
var accessMethodOID;

if(accessDescription.accessMethod === 'caIssuers') {
// handle caIssuers
accessMethodOID = oids['caIssuers'];
} else if(accessDescription.accessMethod === 'ocsp') {
// handle ocsp
accessMethodOID = oids['ocsp'];
} else {
throw new Error(
'Invalid "authorityInfoAccess" accessMethod with value "'
+ accessDescription.accessMethod + '"');
}

const oidDer = asn1.oidToDer(accessMethodOID);
value.value.push(
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, oidDer));

const uri = accessDescription.accessLocation;
// Consider that the accessLocation is always a URI
value.value.push(asn1.create(
asn1.Class.CONTEXT_SPECIFIC, 6, false, uri
));

e.value.value.push(value);
}
} else if(e.name === 'cRLDistributionPoints') {
e.value = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
var seq = e.value.value;
Expand Down
Loading