decentralized-identity/did-auth-jose

JWS / Secp256k1CryptoSuite uses wrong signature algorithm (secp384k1)

Opened this issue · 4 comments

awoie commented

I generated a JWK and JWS using the DIF did-auth-jose stack. The JWS can be verified by the DIF did-auth-jose implementation itself but the resulting JWS cannot be verified by uPort's and Nimbus' JWT implementation. I assume there might be a bug in how the JWS signature is generated. NOTE, uPort's and Nimbus' JWS implementation can verify each other.

Updated:
Ok, we found the reason and it is a bug in did-auth-jose. The underlying ec-key package uses secp384k1 instead of secp256k1 (see https://www.npmjs.com/package/ec-key). This is also the reason why did-jose-auth's signatures are way longer than uPort's. ec-key does not support secp256k1. An alternative is elliptic which has support for secp256k1.

First, I generated a JWS as follows:

const ecPrivateKey = await jwt.EcPrivateKey.generatePrivateKey('k1234')
const factory = new CryptoFactory([new Secp256k1CryptoSuite()]);
console.log("DIF did-auth-jose: key: " + JSON.stringify(ecPrivateKey));

const jwsUnsigned = factory.constructJws({
    iss: "oliver"
});

const jwsSigned64 = await jwsUnsigned.sign(ecPrivateKey);
console.log("DIF did-auth-jose: jws signed: " + jwsSigned64);

const jwsSigned = factory.constructJws(jwsSigned64);

const verifiedData = await jwsSigned.verifySignature(ecPrivateKey.getPublicKey());
console.log("DIF did-auth-jose: jws verified payload: " + verifiedData);

Note, jwsSigned.verifySignature returns the verified payload. So, the DIF did-auth-jose implementation can verify JWS that it generated.

Then, I tried to verify the resulting JWS with uPort and Nimbus and both failed to verify the JWS.

I used the following parameter:

// Public key generated by DIF did-auth-jose
x = "_5dtWlDefzUJdbQGH5r-nMzu1TeAuFx0gD0W5qFu9es";
y = "9Mq51i3P0YV86uvc3dS4FxbxeLPNUA3KGtwf6bd4pt8";

// JWS generated by DIF did-auth-jose
jwt64 = "eyJhbGciOiJFUzI1NksiLCJraWQiOiJrMTIzNCJ9.eyJrZXkxIjoidmFsdWUiLCJpc3MiOiJvbGl2ZXIifQ.MEYCIQDwcjffaSLetwLyyAJHFDrcf1wFDVjQiY6ouzMtsg0yTwIhAO65sYMmpgbM5bNJfJzluvWHMMsEhgwBLizEqN3GxbSZ";

The following is how I verified the JWS using Nimbus and it failed to verify the DIF did-auth-jose JWS:

import java.text.ParseException;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.jwk.*;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jose.util.Base64URL;
import com.nimbusds.jwt.*;

public class App {

    public static void main(String[] args) throws JOSEException, ParseException {

        String x = "_5dtWlDefzUJdbQGH5r-nMzu1TeAuFx0gD0W5qFu9es";
        String y = "9Mq51i3P0YV86uvc3dS4FxbxeLPNUA3KGtwf6bd4pt8";
        String jwt64 = "eyJhbGciOiJFUzI1NksiLCJraWQiOiJrMTIzNCJ9.eyJrZXkxIjoidmFsdWUiLCJpc3MiOiJvbGl2ZXIifQ.MEYCIQDwcjffaSLetwLyyAJHFDrcf1wFDVjQiY6ouzMtsg0yTwIhAO65sYMmpgbM5bNJfJzluvWHMMsEhgwBLizEqN3GxbSZ";

        ECKey publicKey = new ECKey.Builder(Curve.P_256K, new Base64URL(x), new Base64URL(y))
                .keyUse(KeyUse.SIGNATURE)
                .keyID("k1234")
                .build();

        SignedJWT jwt = SignedJWT.parse(jwt64);

        // Verify the ES256K signature with the public EC key
        if (jwt.verify(new ECDSAVerifier(publicKey))) {
            System.out.println("verified");
        } else {
            System.out.println("not verified");
        }

        System.out.println(jwt.getJWTClaimsSet().toJSONObject());
    }
}

The Nimbus code above verifies the following uPort generated JWS with the following parameter successfully:

x = "3B3QsivGGB6BMnswdCQCgqLnqHNCG7nsbfuR_0duvFQ";
y = "cs-IKFNFBYam65jN-1QB-Y1S27ur-CkEnPMQLLSlkFA";
jwt64 = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE1NjA3MTA4NTAsImtleTEiOiJ2YWx1ZSIsImlzcyI6Im9saXZlciJ9.qyTMlB3YMnT_2tRTi8Zy8SyuHuqDD5cas-az8X2VsxUyJr7BMCCHZ1NzF06UR1z55pE-UxGhhSbonqbU3wauTg";

However, the same uPort JWS cannot be verified by DIF did-auth-jose by doing the following:

const jwsSigned = factory.constructJws(jwtUport);
const verifiedData = await jwsSigned.verifySignature(ecPrivateKey.getPublicKey());
console.log("uPort jwsVerified: " + verifiedData);
awoie commented

@dstrockis @codeglobally Any update on this?

In this pr #37, I made some updates, and tested in node and browser. @awoie you can test it

I'd recommend against using elliptic for a production environment. After digging into it, I found that it's not one of the design goals to prevent side-channel attacks. See here for details. I've yet to find another native JS library that supports Secp256k1. I did see that there's a wasm bundled package - bitcoin-ts, but this has the downside of being rather large.

The current direction I'm thinking would be useful is to use the node.js crypto APIs. The downside to this is there's not browser support (could be possible with browserify - but I haven't investigated that yet). I've also thought about using the wasm build of Ursa. The downside there is that it's not published to npm at this point.

found an alternative repo that can be used: https://www.npmjs.com/package/noble-secp256k1