Skip to content

Commit

Permalink
Add RS256 algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
Philipp Jahoda committed Feb 28, 2022
1 parent d4e4ba7 commit 4b447f1
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 124 deletions.
Binary file modified .DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

97 changes: 10 additions & 87 deletions .idea/workspace.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ Lightweight Kotlin JWT implementation (Json Web Token) designed for **Apple**, a

No other dependencies required.

## Algorithms supported
- ES256
- RS256

## Dependency

Add the following to your **build.gradle** file:
Expand All @@ -16,7 +20,7 @@ allprojects {
}
dependencies {
implementation 'com.github.PhilJay:JWT:1.1.5'
implementation 'com.github.PhilJay:JWT:1.2.0'
}
```

Expand All @@ -33,7 +37,7 @@ Or add the following to your **pom.xml**:
<dependency>
<groupId>com.github.PhilJay</groupId>
<artifactId>JWT</artifactId>
<version>1.1.5</version>
<version>1.2.0</version>
</dependency>
```

Expand Down Expand Up @@ -78,15 +82,18 @@ Create required encoders, decoders and JSON Mapper (e.g. Gson or equivalent). Th
}
```

Create the token by providing your teamId, keyId and secret (private key excluding header and footer). The teamId can be obtained from the developer member center. The keyId can be obtained when you create your secret (private key).
Create the Apple JWT token by providing your teamId, keyId and secret (private key excluding header and footer). The teamId can be obtained from the developer member center. The keyId can be obtained when you create your secret (private key).

```kotlin
val token = JWT.token("teamId", "keyId", "secret", jsonEncoder, encoder, decoder)
val token = JWT.tokenApple("teamId", "keyId", "secret", jsonEncoder, encoder, decoder)
```

// or...
Create any JWT token by providing the required algorithm, header, payload and secret (private key):

```kotlin
val header = JWTAuthHeader(...)
val payload = JWTAuthPayload(...)
val token = JWT.token(header, payload, "secret", jsonEncoder, encoder, decoder)
val token = JWT.token(Algorithm.ES256, header, payload, "secret", jsonEncoder, encoder, decoder)
```

## Decoding JWT
Expand Down
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins {
}

group 'com.philjay.jwt'
version '1.1.5'
version '1.2.0'

sourceCompatibility = 1.8

Expand All @@ -16,7 +16,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testCompile group: 'junit', name: 'junit', version: '4.12'

testCompile 'com.google.code.gson:gson:2.8.6'
testCompile 'com.google.code.gson:gson:2.9.0'
testCompile 'commons-codec:commons-codec:1.14'
}

Expand Down
54 changes: 33 additions & 21 deletions src/main/java/com/philjay/jwt/JWT.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,55 +16,54 @@ import kotlin.text.Charsets.UTF_8


object JWT {

private const val verifyAlgorithm = "SHA256withRSA"
private const val tokenSignatureAlgorithm = "SHA256withECDSA"
private const val keyAlgorithm = "EC"
private const val tokenDelimiter = '.'

/**
* Generates a JWT token as per Apple's specifications. Does not include the required "bearer" prefix.
*
* @param teamId The team identifier (can be obtained from the developer console member center)
* @param keyId The key identifier (can be obtained when generating your private key)
* @param secret The private key (without the header and the footer - as in (BEGIN KEY...)
* @param secret The private key (without the header and the footer - as in BEGIN KEY...)
* @param jsonEncoder: A mapper to transform JWT header and payload to a json String.
* @param encoder An encoder to base64 encode the JWT header and payload json String.
* @param decoder A decoder to base64 decode ByteArrays.
* @param charset The Charset to use for String to ByteArray encoding, defaults to UTF_8.
* @return A valid JWT token.
*/
fun token(
fun tokenApple(
teamId: String,
keyId: String,
secret: String,
jsonEncoder: JsonEncoder<JWTAuthHeader, JWTAuthPayload>,
jsonEncoder: JsonEncoder<AppleJWTAuthHeader, JWTAuthPayload>,
encoder: Base64Encoder,
decoder: Base64Decoder,
charset: Charset = UTF_8
): String {

val now = Instant.now().epochSecond // token timestamp in seconds

val header = JWTAuthHeader(kid = keyId)
val header = AppleJWTAuthHeader(kid = keyId)
val payload = JWTAuthPayload(teamId, now)

return token(header, payload, secret, jsonEncoder, encoder, decoder, charset)
return token(Algorithm.ES256, header, payload, secret, jsonEncoder, encoder, decoder, charset)
}

/**
* Generates a JWT token as per Apple's specifications. Does not include the required "bearer" prefix.
* Generates a JWT token String.
*
* @param algorithm The algorithm to use.
* @param header The auth header usually containing algorithm and key id.
* @param payload The payload usually containing at least the team id and timestamp.
* @param secret The private key (without the header and the footer - as in (BEGIN KEY...)
* @param secret The private key (without the header and the footer - as in BEGIN KEY...)
* @param jsonEncoder: A mapper to transform JWT header and payload to a json String.
* @param encoder An encoder to base64 encode the JWT header and payload json String.
* @param decoder A decoder to base64 decode ByteArrays.
* @param charset The Charset to use for String to ByteArray encoding, defaults to UTF_8.
* @return A valid JWT token.
*/
fun <H : JWTAuthHeader, P : JWTAuthPayload> token(
algorithm: Algorithm,
header: H, payload: P, secret: String, jsonEncoder: JsonEncoder<H, P>, encoder: Base64Encoder,
decoder: Base64Decoder, charset: Charset = UTF_8
): String {
Expand All @@ -77,7 +76,7 @@ object JWT {

val value = "$base64Header$tokenDelimiter$base64Payload"

return value + tokenDelimiter + es256(secret, value, encoder, decoder, charset)
return value + tokenDelimiter + sign(algorithm, secret, value, encoder, decoder, charset)
}

/**
Expand Down Expand Up @@ -115,9 +114,9 @@ object JWT {
}

/**
* Verifies the provided JWT String with the provided JWK object (public key).
* Verifies the provided JWT String with the provided JWK object (RSA public key).
* @param jwt: The JWK String to validate.
* @param jwk: The Json Web Key (public key) obtained from Apple for validation.
* @param jwk: The Json Web Key (RSA public key) obtained from Apple for validation.
* @param decoder: Base64 decoder for decoding the JWT signature.
* @return True if validation was successful, false if not.
*/
Expand Down Expand Up @@ -147,26 +146,31 @@ object JWT {
}
}

private fun es256(
private fun sign(
algorithm: Algorithm,
secret: String,
data: String,
encoder: Base64Encoder,
decoder: Base64Decoder,
charset: Charset
): String {

val factory = KeyFactory.getInstance(keyAlgorithm)
val factory = KeyFactory.getInstance(algorithm.keyAlg)
val keySpec = PKCS8EncodedKeySpec(decoder.decode(secret.toByteArray(charset)))
val key = factory.generatePrivate(keySpec)

val algECDSAsha256 = Signature.getInstance(tokenSignatureAlgorithm)
algECDSAsha256.initSign(key)
algECDSAsha256.update(data.toByteArray(charset))
val sig = Signature.getInstance(algorithm.alg)
sig.initSign(key)
sig.update(data.toByteArray(charset))

return encoder.encodeURLSafe(algECDSAsha256.sign())
return encoder.encodeURLSafe(sig.sign())
}
}

enum class Algorithm(val alg: String, val keyAlg: String) {
ES256("SHA256withECDSA", "EC"), RS256("SHA256withRSA", "RSA")
}

/**
* Mapper to transform auth header and payload to a json String.
*/
Expand Down Expand Up @@ -299,11 +303,19 @@ open class JWKObject(
* JWT Authentication token header.
*/
open class JWTAuthHeader(
/** the encryption algorithm used */
val alg: String
)

/**
* JWT Authentication token header for Apple.
*/
class AppleJWTAuthHeader(
/** the encryption algorithm used, defaults to ES256 */
val alg: String = "ES256",
alg: String = Algorithm.ES256.name,
/** the key identifier (found when generating private key) */
val kid: String
)
) : JWTAuthHeader(alg)

/**
* JWT authentication token payload.
Expand Down
Loading

0 comments on commit 4b447f1

Please sign in to comment.