PKIJS signature and verification don't match

53 Views Asked by At

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);
    }
    
}
1

There are 1 best solutions below

0
lviggiani On

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"])