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

Only 8, 16, 24, or 32 bits supported: 248 #1059

Open
willfurnell opened this issue Dec 7, 2023 · 4 comments
Open

Only 8, 16, 24, or 32 bits supported: 248 #1059

willfurnell opened this issue Dec 7, 2023 · 4 comments

Comments

@willfurnell
Copy link

Hello,

I'm trying to decrypt a private key but am getting the following error when trying to decrypt - I was wondering if anyone has seen this before please?

Weirdly, this only seems to be from 'certain' private keys - we have lots of users that decrypt without issue, but every so often we get this error from Forge,

Thanks,

Will.

requestdownload:524 Error: Error: Only 8, 16, 24, or 32 bits supported: 248
    at s (forge.min.js:1:1)
    at i.ByteStringBuffer.getInt (forge.min.js:1:1)
    at forge.min.js:1:1
    at e (forge.min.js:1:1)
    at n.fromDer (forge.min.js:1:1)
    at s.decryptPrivateKeyInfo (forge.min.js:1:1)
    at s.decryptRsaPrivateKey (forge.min.js:1:1)
    at HTMLAnchorElement.<anonymous> (requestdownload:501:54)
    at HTMLAnchorElement.dispatch (jquery.min.js:2:43064)
    at v.handle (jquery.min.js:2:41048)

The code used is as follows:

    const fileSelector = document.getElementById('privateKeyPicker');
    fileSelector.addEventListener('change', () => {
        const fileReader = new FileReader();
        fileReader.onloadend = (function (loadEvent) {
            let textFromFileLoaded = loadEvent.target.result;
            $("#certkey").val(textFromFileLoaded);
            keyValid();
        });
        fileReader.readAsText(document.getElementById('privateKeyPicker').files[0])
    });

    function downloadCert(b64p12) {
        const myBuffer = base64DecToArr(b64p12).buffer;
        const p12blob = new Blob([myBuffer], {type: 'application/x-pkcs12'});
        saveAs(p12blob, "certBundle.p12");
    }

    $(document).ready(function () {
        $("#createP12Button").click(function () {
            //error checking to ensure all fields are filled in
            if ($('#keypass').val() === "" && $('#certkey').val() === "=") {
                alert("Please enter private key password and use 'Choose File' and 'Load File' buttons to select private key (or paste private key into textarea)");
            } else if ($('#keypass').val() === "") {
                alert("Please enter private key password");
            } else if ($('#certkey').val() === "") {
                alert("Please use 'Choose File' and 'Load File' buttons to select your private key (or paste private key into textarea)");
            } else {
                //regex to ensure only private key used from text pasted in
                const regexPrivKey = $("#certkey").val().match(/(-----BEGIN \b(ENCRYPTED|RSA)\b PRIVATE KEY-----)[\s\S]*(-----END \b(ENCRYPTED|RSA)\b PRIVATE KEY-----)/);
                if (regexPrivKey === null) {
                    alert("Private key not found, please ensure correct file has been selected or pasted into text area.");
                } else {
                    const _pem = {
                        certificateDev: $("#certpem").val(),
                        privateKeyDev: regexPrivKey[0]
                    };
                    // Try to parse certificate and private key object
                    try {
                        const cert = forge.pki.certificateFromPem(_pem.certificateDev, true);
                        const password = $('#keypass').val();
                        const privateKey = forge.pki.decryptRsaPrivateKey(_pem.privateKeyDev, password);
                        if (privateKey !== null) {
                            if (privateKey.n.toString() !== cert.publicKey.n.toString()) {
                                alert("Private and public key modulo do not match. Are you sure you uploaded the correct file?")
                            } else {
                                const chain = [cert];
                                //create PKCS12
                                console.log('\nCreating PKCS#12...');
                                const newPkcs12Asn1 = forge.pkcs12.toPkcs12Asn1(privateKey, chain, password,
                                    {generateLocalKeyId: true, friendlyName: 'myUkCaCertificate', algorithm: '3des'});
                                const newPkcs12Der = forge.asn1.toDer(newPkcs12Asn1).getBytes();
                                console.log('\nBase64-encoded new PKCS#12:');
                                const b64p12 = forge.util.encode64(newPkcs12Der);
                                downloadCert(b64p12);
                            }
                        } else {
                            alert("Please enter the correct password for the private key");
                        }
                    } catch (ex) {
                        alert("Could not create private key object - please contact support@grid-support.ac.uk");
                        if (ex.stack) {
                            console.log('Error name:', ex.name);
                            console.log('Error message:', ex.message);
                            console.log('Error:', ex);
                            console.log(ex.stack);
                        } else {
                            console.log('Error name:', ex.name);
                            console.log('Error message:', ex.message);
                            console.log('Error:', ex);
                        }
                    }
                }
            }
        });

    });  // end on ready
@davidlehn
Copy link
Member

Unfortunate it's a submitted private key issue. Do you happen to have an example test case that isn't secret?

Based on that error it would seem like some DER decoding issue. Maybe there are some edge cases out there that trigger a bug. Or could be bad input to start with and the error is poor. Hard to debug such things without an test case.

@willfurnell
Copy link
Author

willfurnell commented Dec 11, 2023

Yes sure - here's a private key that's been revoked

-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIE6jAcBgoqhkiG9w0BDAEDMA4ECKPGG1SWab+cAgIIAASCBMjLabq/o+YaxNEL
bIcGr/2oO7QfLY2ebondYNqbfF5Ou7t8sEyE1nDZNOdiVRLng29b7CDnlFMOKA6X
Xi30z7Vh61GPsdyfvEEEUU39V26tgpud3NRApJzIcUQ1DkpRQKgKTpo+tx+Qe1z4
ZYKotgIoXl8sWOEJbKMkoGoEfZzbugmbNAO0ICCByIGJoVmFXl8o5D9qhCCaCB1W
XjvDFtLqxNjFlAxrN93vJ2dGYvc+OXId31TciMPQeckzdtLC5UrfDVVaBom7ueWV
OVo+jmHE/Yg5cuZGWM/s0xygmpfJJ7UsTCpiV4ctFf8uRB5AsM0wdmNl/DK9lhD+
lYt2UfztS58Z/KDPPzoJD5rSEyO9FKRx+QPcAhJBY4l7t+bAZneE2w5ALe+vfp0A
KIW+/rCpNnhA70SdIR9FfkcusQsn540kmv99+AFbX2VQmJ15m7UXw7c/uYrkeY+0
Pyn4tKb2hz6E3J9AhBQPCF44RwVnLlrQrJpL2v+NleXKIHs/rVGGi0Gby3Bzxb5S
9+uKYK7hpsc0F53YBoljS5xw1m8xU37q04JQ8BKYwZpOuMTYcQt6Q4aoHmb+6li2
hzVjTXKtmvRRju6rl9wi2djJUPxNT1xU+JL80EMh/KUUA6X3bDqFYJwTKrAbEU+7
4du4QLo4hdRqsDq2KjKcbmgs2tAVBAdSWNtXyAzpFHyY/5r64LSpuMAG0lJczUaM
0i8dUydAyLE9W6xFtKbCVhT3UhyWbt2pxQw2unf2gFwilzSV8V70gAIQSU1z593C
IT/tnkhhKCstnrTEGN/xMeas1O2uYrUkiFFflryIv7twSZbD49+jP2Vv9h0lpvnV
T6Q4Gq93b9zdZ1igfzhWrz5dlt6rmpTYv9xnh52Ro8Vm/3J6rjveJo+2Z2ba7Wl4
0+83M4RWzRVJKU2qMVQvRH5mifDV2d1VHpyyXkdNxqR85VjHDQ+PhSiKYQPPmnLc
5sKoLkJiyMGinENh8L73XH8ZpkBqBgzbFNTsFiEFC+S2xuLVTXtfp9yIP73RA2GS
zZniOfvDoq2b23QG2s5IzTpgP3qAxmXjVK3cGaDV3YFAWVmwDnuizGcATqB/MbpP
QN4VfdEaoRpHYvivnLBzNK4gNIDrBjoFtlhBczGfoFcrHacegqQpYt2SGPJz/OND
kK1kp1bqC1KenoosAH8HBVqDsWATxwByU1z4spCJPe1tPrE5VyXsffL4Da4p/J3I
tnG6OlYrZJJxQ1Gt1vvjWeYidWQLkZlAtklNnRE0db7iBHV4ygkWRQVYbYsZoYbA
rJQohx2ULroUswsJ1c5yqpa6CR4egLvDNniEDCvaDTJO/OQAL1H+gMH04N4wy1Fy
ORijc5GG0AmV8viqjrI+4rTJ8X1pekGmyvGj9kk2cHcbn62eefY1LD+4ym89bChP
XiDZU8tNapR6c34U3cWMqWruQzMJfnLGp8Fqz38hzf3IwYWYwMjoB1GOQI8LFwN0
5y5F4PjG0WrUpek/4yfnc7DVeYM0MSczOzPNDPg6mzj8lp6FcIEykMoI3ugPJCFa
05vCAr6c4HWtcT3VTy+6ZgXL1FZaPEnxL8cb7us+SNtHpRSPaTkfui2PFGLe2uKf
2fiZMc0hL1oYeZK3Ffg=
-----END ENCRYPTED PRIVATE KEY-----

password Computinggraduate

and it was generated with the following code:


/**
 * Create a new CSR using Forge JS with a 2048 key strength.
 *
 * @param pw
 * @param cn
 * @param ou
 * @param loc
 * @param o
 * @param c
 * @param pw
 */
function createCSR(cn, ou, loc, o, c, pw) {

    console.log('Generating 2048-bit key-pair...');
    const keys = forge.pki.rsa.generateKeyPair({bits: 2048, workers: -1});
    console.log('Key-pair created.');

    console.log('Creating certification request (CSR) ...');
    const csr = forge.pki.createCertificationRequest();
    csr.publicKey = keys.publicKey;
    // Notice we have to create the CSR with a DN that has the
    // following RDN structure order (C, O, OU, L, CN)
    // (i.e. starting with C ending with CN)
    csr.setSubject([
        {
            name: 'countryName',
            value: c
        },
        {
            name: 'organizationName',
            value: o
        },
        {
            shortName: 'OU',
            value: ou
        },
        {
            name: 'localityName',
            value: loc
        },
        {
            name: 'commonName',
            value: cn
        }
    ]);


    // sign certification request, with SHA256 signature (Rocky 9 requirement)
    csr.sign(keys.privateKey, forge.md.sha256.create());
    console.log('Certification request (CSR) created.');

    // PEM-format keys and csr
    // Annoyingly, MacOS Keychain required 3des, which I know isn't ideal, but we need to support Macs
    const algOpts = {algorithm: '3des'};
    const pem = {
        //privateKey: forge.pki.privateKeyToPem(keys.privateKey),
        privateKey: forge.pki.encryptRsaPrivateKey(keys.privateKey, pw, algOpts),
        publicKey: forge.pki.publicKeyToPem(keys.publicKey),
        csr: forge.pki.certificationRequestToPem(csr)
    };

    return pem;
}

@davidlehn
Copy link
Member

I poked at this for a few minutes. Not sure what the issue is. Unfortunately I don't have time to deep dive debug it at the moment. But here are some thoughts:

  • At first glance, it seems like it could be a simple incorrect password? Are you sure that's the right one?
  • The error paths in some of the related code here can make it difficult to debug why things are failing.
  • If you give it a password that decodes the data into something that is close enough to be able to be processed as DER, the decode process might go on until it fails due to bounds or range checking or similar like it does here.
  • If you use most other passwords, it will return null for the top level call.
  • I greatly simplified your code into some files. They can be useful to test this sort of thing.

This test does a simple decrypt of the test static pem and password. It always fails. Most other passwords I tested will return null.

const forge = require('node-forge');

const pem = `\
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIE6jAcBgoqhkiG9w0BDAEDMA4ECKPGG1SWab+cAgIIAASCBMjLabq/o+YaxNEL
bIcGr/2oO7QfLY2ebondYNqbfF5Ou7t8sEyE1nDZNOdiVRLng29b7CDnlFMOKA6X
Xi30z7Vh61GPsdyfvEEEUU39V26tgpud3NRApJzIcUQ1DkpRQKgKTpo+tx+Qe1z4
ZYKotgIoXl8sWOEJbKMkoGoEfZzbugmbNAO0ICCByIGJoVmFXl8o5D9qhCCaCB1W
XjvDFtLqxNjFlAxrN93vJ2dGYvc+OXId31TciMPQeckzdtLC5UrfDVVaBom7ueWV
OVo+jmHE/Yg5cuZGWM/s0xygmpfJJ7UsTCpiV4ctFf8uRB5AsM0wdmNl/DK9lhD+
lYt2UfztS58Z/KDPPzoJD5rSEyO9FKRx+QPcAhJBY4l7t+bAZneE2w5ALe+vfp0A
KIW+/rCpNnhA70SdIR9FfkcusQsn540kmv99+AFbX2VQmJ15m7UXw7c/uYrkeY+0
Pyn4tKb2hz6E3J9AhBQPCF44RwVnLlrQrJpL2v+NleXKIHs/rVGGi0Gby3Bzxb5S
9+uKYK7hpsc0F53YBoljS5xw1m8xU37q04JQ8BKYwZpOuMTYcQt6Q4aoHmb+6li2
hzVjTXKtmvRRju6rl9wi2djJUPxNT1xU+JL80EMh/KUUA6X3bDqFYJwTKrAbEU+7
4du4QLo4hdRqsDq2KjKcbmgs2tAVBAdSWNtXyAzpFHyY/5r64LSpuMAG0lJczUaM
0i8dUydAyLE9W6xFtKbCVhT3UhyWbt2pxQw2unf2gFwilzSV8V70gAIQSU1z593C
IT/tnkhhKCstnrTEGN/xMeas1O2uYrUkiFFflryIv7twSZbD49+jP2Vv9h0lpvnV
T6Q4Gq93b9zdZ1igfzhWrz5dlt6rmpTYv9xnh52Ro8Vm/3J6rjveJo+2Z2ba7Wl4
0+83M4RWzRVJKU2qMVQvRH5mifDV2d1VHpyyXkdNxqR85VjHDQ+PhSiKYQPPmnLc
5sKoLkJiyMGinENh8L73XH8ZpkBqBgzbFNTsFiEFC+S2xuLVTXtfp9yIP73RA2GS
zZniOfvDoq2b23QG2s5IzTpgP3qAxmXjVK3cGaDV3YFAWVmwDnuizGcATqB/MbpP
QN4VfdEaoRpHYvivnLBzNK4gNIDrBjoFtlhBczGfoFcrHacegqQpYt2SGPJz/OND
kK1kp1bqC1KenoosAH8HBVqDsWATxwByU1z4spCJPe1tPrE5VyXsffL4Da4p/J3I
tnG6OlYrZJJxQ1Gt1vvjWeYidWQLkZlAtklNnRE0db7iBHV4ygkWRQVYbYsZoYbA
rJQohx2ULroUswsJ1c5yqpa6CR4egLvDNniEDCvaDTJO/OQAL1H+gMH04N4wy1Fy
ORijc5GG0AmV8viqjrI+4rTJ8X1pekGmyvGj9kk2cHcbn62eefY1LD+4ym89bChP
XiDZU8tNapR6c34U3cWMqWruQzMJfnLGp8Fqz38hzf3IwYWYwMjoB1GOQI8LFwN0
5y5F4PjG0WrUpek/4yfnc7DVeYM0MSczOzPNDPg6mzj8lp6FcIEykMoI3ugPJCFa
05vCAr6c4HWtcT3VTy+6ZgXL1FZaPEnxL8cb7us+SNtHpRSPaTkfui2PFGLe2uKf
2fiZMc0hL1oYeZK3Ffg=
-----END ENCRYPTED PRIVATE KEY-----
`;
const pw = 'Computinggraduate';

try {
  const privateKey = forge.pki.decryptRsaPrivateKey(pem, pw);
  console.log('PASS');
  console.log(privateKey);
} catch (ex) {
  console.log('FAIL');
  console.error(ex);
}

If you want to find a key that does something weird, you can loop around with different prefixes just to get nonsense decrypted data. For instance, the below finds a non-null decode for "t17" and fails for "Unparsed DER bytes remain after ASN.1 parsing".

let res = null;
let i = 0;
const prefix = 't';
while(res === null) {
  console.log(i);
  res = forge.pki.decryptRsaPrivateKey(pem, prefix + i++);
}

This test does what I think you were doing. It will create private key pem and they try to decrypt it. Uses different keys and passwords to try and trigger and weird data issues. It just keeps looping and not failing.

const forge = require('node-forge');

let i = 0;
let run = 1;
while(run) {
  const keys = forge.pki.rsa.generateKeyPair({bits: 2048, workers: -1});
  const algOpts = {algorithm: '3des'};
  const pw = 'Computinggraduate' + i;
  const pem = forge.pki.encryptRsaPrivateKey(keys.privateKey, pw, algOpts);
  console.log(i++);
  console.log(pem);
  const privateKey = forge.pki.decryptRsaPrivateKey(pem, pw);
  //console.log(privateKey);
  if(!privateKey) {
    console.log('FAIL');
    run = 0;
  }
}

I'm not sure what the best behavior is here. Maybe the code should catch any errors and return null? It's likely rare people have bad DER that can't parse into ASN.1, but it could happen.

It is possible there is bug in either the encryption process or decryption process that gets triggered with the test data. But I'm not really sure how to tell.

Do you have any more failing test data?

@willfurnell
Copy link
Author

willfurnell commented Jan 3, 2024

I'm sure the password is correct, this is not the only user that has had this problem.
Unfortunately I don't have any more test data to hand. Whilst I can probably get bad private keys (as they're bad anyway), people tend to re-use passwords, which has happened in previous cases so I've been unable to ask for them :(
Would an encrypted private key without the corresponding password be any use to you?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants