Skip to content

uport-project/kotlin-did-jwt

Repository files navigation

kotlin-did-jwt

CircleCI Twitter Follow

This is the Kotlin implementation of the basic JWT methods for DID-JWTs

FAQ and helpdesk support

did-jwt

The kotlin-did-JWT library allows you to sign and verify JSON Web Tokens (JWT) using ES256K, and ES256K-R algorithms.

Public keys are resolved using the Decentralized ID (DID) of the signing identity of the claim, which is passed as the iss attribute of the encoded JWT.

DID methods

We currently support the following DID methods:

Defaults are automatically installed but you can customize to fit your needs.

Support for other DID methods should be simple. Write a DID resolver supporting the DIDResolver interface. Install it using

val resolver : DIDResolver = DIDResolver.Builder
.addResolver(ethrDidResolver)
.addResolver(/*...*/)
.build()

Once you've verified that it works, feel free to advertise it in the list above so people can find it.

If your DID method requires a different signing algorithm than what is already supported, please create a PR.

Installation

The libraries built here are distributed through jitpack

In your main build.gradle file, add:

allprojects {
    repositories {
        maven { url 'https://jitpack.io' }
        //...
    }
}

In your application build.gradle file, add:

def did_jwt_version = "0.4.0"
dependencies {
    //...
    implementation "com.github.uport-project.kotlin-did-jwt:jwt:$did_jwt_version"
}

Example

1. Create a did-JWT

In practice you should secure the key passed to KPSigner. The key provided in code below is for informational purposes.

val jwt = JWTTools()
//...
val payload = mapOf(
        "claims" to mapOf("name" to "R Daneel Olivaw")
)

val signer = KPSigner("0x54ece214d38fe6b46110a21c69fd55230f09688bf85b95fc7c1e4e160441ece1")
val issuerDID = "did:ethr:${signer.getAddress()}"

val token = jwt.createJWT(payload, issuerDID, signer)

2. Decode a did-JWT

Try decoding the JWT. You can also do this using jwt.io

val (header, payload, sig) = jwt.decodeRaw("eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJjbGFpbXMiOnsibmFtZSI6IlIgRGFuZWVsIE9saXZhdyJ9LCJpYXQiOjEyMzQ1Njc4LCJleHAiOjEyMzQ1OTc4LCJpc3MiOiJkaWQ6ZXRocjoweDQxMjNjYmQxNDNiNTVjMDZlNDUxZmYyNTNhZjA5Mjg2YjY4N2E5NTAifQ.o6eDKYjHJnak1ylkpe9g8krxvK9UEhKf-1T0EYhH8pGyb8MjOEepRJi8DYlVEnZno0DkVYXQCf3u1i_HThBKtAA")

The decoded payload resembles:

mapOf(
    "claims" to mapOf("name" to "R Daneel Olivaw"),
    "iat" to 1.2345678E7,
    "exp" to 1.2345978E7,
    "iss" to "did:ethr:0x4123cbd143b55c06e451ff253af09286b687a950"
)

You can also use jwt.decode("<token>") to get a JwtPayload object instead of a map but that is a more rigid structure and will be phased away in future releases.

3. Verify a did-JWT

val resolver = EthrDIDResolver.Builder()
                               .addNetwork(EthrDIDNetwork("<name>", "<registryAddress>", "<JsonRPC>"))
                               .build()
                               
val payload : JwtPayload = JWTTools().verify("<token>", resolver)

If the token is valid, the method returns the decoded payload, otherwise throws a InvalidJWTException or JWTEncodingException

This behavior is subject to change in an upcoming release The verify() method will return a higher level abstraction that will contain the payload and more.

The function requires a DIDResolver which will be used to resolve DIDs during the verification

Verifying a token means checking that the signature was produced by a key associated with the issuer DID (iss field).

This association is resolved by a DID resolver, which can produce a DIDDocument listing various public keys and service endpoints for a given DID.

Audience verification

If the token contains a non null aud field, an additional soft-check is performed to match the verification against an intended audience. This same aud DID must be supplied to the verify() method for the token to be marked as valid (after passing all the cryptographic checks as well).

Generally your app will have its own DID which should always be passed to the verify method so that only tokens intended for your app are considered valid.

CHANGELOG

  • 0.4.0
    • removed deprecated components ( #46 )
  • 0.3.6
    • fix: okhttp dependency issue (#43)
    • docs: mark UportDIDResolver as deprecated (f5ffad34)
    • refactor: code cleanup, enforcing detekt on PRs (#45)
  • 0.3.5
    • feat: add credential status / revocation support (#35)(#42)
    • support: bump dependencies (kotlin 1.3.70, kethereum 0.81.4) (#44)
  • 0.3.4
    • feat: deprecate UniversalResolver singleton (#31)(#34)(#37)
  • 0.3.3
    • refactor: use kethereum 0.76.2 ( 1f730e39 )
    • bugfix: resolve publicKey entries with null chars in their names ( #27 )
  • 0.3.2
    • feat: support multi-network ethr-did ( #20 )
    • bugfix: crash in ethr-did-resolver when nodes don't reply with logs ( #21 )
    • refactor: use kethereum 0.76.1 and kotlin-common 0.3.1 ( #22 )
    • refactor: use lowercase coordinates for kethereum libs ( #25 )
  • 0.3.1
    • allow override or removal of iat field when creating JWTs (#17)
  • 0.3.0
    • [breaking] remove deprecated https-did module, now replaced by web-did (#14)
    • add test coverage metrics (#10)
    • add support for arbitrary maps with @Serializers as JWT payloads (#16)
    • remove moshi dependency (#16)
  • 0.2.1
    • add support for web DID, deprecating https DID (#5)
    • allow creation of JWTs with no expiry (#6)
    • fallback to ES256K-R style verification if ES256K algorithm fails because of missing key encoding (#7)
    • [bugfix] delegate keys in ethr-did documents were not being resolved properly (#9)
  • 0.2.0
    • [breaking] add audience checking for JWT verification (#2)
    • add jwt-test module with helpers for testing
  • 0.1.2
    • fix crash when parsing legacy identity document
  • 0.1.1
    • initial stable release isolating the did-jwt implementation in kotlin along with resolvers