Skip to content

Commit

Permalink
.NET interoperability for X509Certificate2
Browse files Browse the repository at this point in the history
  • Loading branch information
Sebastian-Dieguez committed Jun 4, 2024
1 parent 2bb97af commit b71c1f0
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 22 deletions.
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
Forge ChangeLog
===============

## 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');
155 changes: 153 additions & 2 deletions 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,7 +520,7 @@ pki.decryptPrivateKeyInfo = function(obj, password) {

// get cipher
var oid = asn1.derToOid(capture.encryptionOid);
var cipher = pki.pbe.getCipher(oid, capture.encryptionParams, password);
var cipher = pki.pbe.getCipher(oid, capture.encryptionParams, (password === undefined)? "": password);

// get encrypted data
var encrypted = forge.util.createBuffer(capture.encryptedData);
Expand Down
62 changes: 42 additions & 20 deletions lib/pkcs12.js
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ function _decryptSafeContents(data, password) {

// get cipher
oid = asn1.derToOid(capture.encAlgorithm);
var cipher = pki.pbe.getCipher(oid, capture.encParameter, password);
var cipher = pki.pbe.getCipher(oid, capture.encParameter, (password === undefined)? "": password);

// get encrypted data
var encryptedContentAsn1 = _decodePkcs7Data(capture.encryptedContentAsn1);
Expand Down Expand Up @@ -790,7 +790,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 +799,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 +922,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 +993,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

0 comments on commit b71c1f0

Please sign in to comment.