PeculiarVentures / PKI.js

PKI.js is a pure JavaScript library implementing the formats that are used in PKI applications (signing, encryption, certificate requests, OCSP and TSP requests/responses). It is built on WebCrypto (Web Cryptography API) and requires no plug-ins.
http://pkijs.org
Other
1.25k stars 204 forks source link

Verification on signed data fails #387

Closed lviggiani closed 7 months ago

lviggiani commented 7 months ago

Consider the following code

    const data = await Deno.readFile("./README.md");
    const certificate = (await loadPEM("./playground/domain.pem"))[0] as Certificate;
    const privateKey = (await loadPEM("./playground/domain-pkcs8-nocrypt.key", "PRIVATE KEY"))[0] as CryptoKey;

    const signedData = await signData(data.buffer, certificate, privateKey);

    // verify same
    const ok = await signedData.verify({
        signer: 0,
        checkChain: true,
        trustedCerts: [certificate],
        data: data.buffer
    })

    console.log(ok); // false :(

    // verify external signed like this
    // openssl cms -sign -signer domain.pem -inkey domain-pkcs8-nocrypt.key -binary -in README.md -outform der -out signature

    const cms = ContentInfo.fromBER(await Deno.readFile("./playground/signature")) as ContentInfo;
    if (cms.contentType !== ContentInfo.SIGNED_DATA)
        throw new Error("CMS is not Signed Data");

    const signedData1 = new SignedData({ schema: cms.content });

    const ok1 = await signedData1.verify({
        signer: 0,
        checkChain: true,
        trustedCerts: [certificate],
        data: data.buffer
    })

    console.log(ok1); // true

In the first part I sign data and then I verify it against loaded certificate and it FAILS

In the second part I load a generated signature with openssl with same certificate and private key used in the first part and the verification against the loaded certificate is OK. So, since verification method is the same in both examples, I guess my signature method has something wrong. Here is the code for signature creation

export async function signData(data:ArrayBuffer, certificate: Certificate, privateKey: CryptoKey):Promise<SignedData>{
    const cmsSigned = new SignedData({
        encapContentInfo: new EncapsulatedContentInfo({
            eContentType: ContentInfo.DATA,
            //eContent: new ans1js.OctetString({ valueHex: data })
        }),
        signerInfos: [
            new SignerInfo({
                sid: new IssuerAndSerialNumber({
                    issuer: certificate.issuer,
                    serialNumber: certificate.serialNumber
                })
            })
        ],
        certificates: [certificate]
    });

    await cmsSigned.sign(privateKey, 0, "SHA-256", data);
    return cmsSigned;
}

Here is how I load certificate and key:

export async function loadPEM(src:string,
    type: PEMType = "CERTIFICATE" ):Promise<PkiObject[] | CryptoKey[]> {
    const buffer = pvtsutils.BufferSourceConverter.toArrayBuffer(await Deno.readFile(src));
    const binary = pvtsutils.Convert.ToBinary(buffer);
    const bers = decodePEM(binary, type);
    const ret = [];
    switch (type) {
      case "CERTIFICATE":
        return bers.map(ber => Certificate.fromBER(ber) as Certificate);
      case "PRIVATE KEY":
        for( const ber of bers) 
            ret.push(await crypto.importKey("pkcs8", ber, {
                name: "RSA-PSS",
                hash: "SHA-256",
                },
                true,
                ["sign"]));
        return ret;
      case "PUBLIC KEY":
        return bers.map(ber => PublicKeyInfo.fromBER(ber) as PublicKeyInfo);
    }

}
lviggiani commented 7 months ago

ok, the problem was the algorithm used while importing the key. By using RSASSA-PKCS1-v1_5 it now works:

crypto.importKey("pkcs8", ber, {
        name: "RSASSA-PKCS1-v1_5",
        hash: "SHA-256"
    },
    true,
    ["sign"])