api-key

TODO

Benefits for use with Inter-Process Communication (IPC)

  • By including the applicationId in the API key data (via keyId), the key can only be utilized for that application.

  • Mitigation of MITM attacks, as the validator application communicates only with the applicationId specified in the API key. If a different application is acting maliciously by using another app's API key, data sent by the validator app are simply dropped.

  • No network required for validation. The API key can be validated offline in a privacy preserving manner.

  • API keys can be checked into Git as they are linked to the applicationId, allowing others building the app from source to have it available.

Usage

  • Extending the ApiKey class

    /**
    * The ApiKey class can be extended to create implementations specific to
    * your RSA key and property types.
    *
    * @see [of]
    * */
    class MyApiKey private constructor(
    encodedApiKey: String,
    publicKey: String,
    ): ApiKey<MyApiKey.Property>(
    encodedApiKey,
    publicKey,
    Property.Companion,
    ) {

    abstract class Property private constructor(encoding: String): ApiKey.Property(encoding) {
    data object PROP11: Property("PROP11")
    class Prop2(val count: Int): Property("PROP2:$count")
    class Unknown internal constructor(encoding: String): Property(encoding)

    companion object: Decoder<Property>() {
    override fun decode(encoding: String): Property = when {
    encoding == PROP11.encoding -> PROP11
    encoding.startsWith("PROP2:") -> Prop2(encoding.substringAfter(':').toInt())
    else -> Unknown(value)
    }
    }
    }

    companion object {

    /**
    * Verifies the signature of and returns [MyApiKey]. This should
    * be called from a background thread.
    *
    * @throws [Exception] when not a valid ApiKey encoding
    * */
    @JvmStatic
    @Throws(Exception::class)
    fun of(encodedApiKey: String): MyApiKey {
    // Using 'of' instead of exposing the constructor
    // allows for signing key rotation, or checks
    // specific to your use-case, without breaking
    // the api for consumers of MyApiKey.

    val key = try {
    MyApiKey(encodedApiKey, RSA_PUBLIC_KEY)

    // In the event of a compromised signing key,
    // can check the returned data for if it was
    // created _after_ the time the signing key
    // was compromised.
    } catch (e: Exception) {
    // In the event signing keys are lost or
    // compromised, can check against the public
    // key of the new signing key here as a fallback.

    throw e
    }

    return key
    }

    const val RSA_PUBLIC_KEY = """
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuWOmpkNrVpBbNn8xct7l
    8u9GnZtFH9G7nlI5a9RX8PKVA8C8yzgYo1QcMJVbLZvVLMryE7hQ0cXMTtuhUBQ3
    DFrAKOyREWpomNyE7IZAqK84FS9EPIkyu1mBQwTmPKKWIFg8PM2ObeKERzEEK8eu
    s+jeQIECSDx7YHDe2LT8yzET03AQf2H8kLL7cQ0Uz0NN7jXUnv08Z7/bJGP+WCMt
    zq60RQSs3zvp5ekO4zB+CwaepiLO6V7v+s7aJHbWbYHUao0Amm8MPKnTacmK8Gzr
    bmLJbHKIOMMhkTHqFHSIamyKEoYu5iGDUeblrnw32DuYIiBdvcAD2zkyFS3pDZ7b
    bwIDAQAB
    """
    }
    }
  • Create the data:

    fun main() {
    val rsaKey: ByteArray = getMyRsaPrivateKey()

    val apiKey = ApiKey.create(
    privateKey = rsaKey,

    // Optionally zero out the RSA PrivateKey after
    // creating the new ApiKey
    clear = true,
    keyId = "com.example",
    properties = setOf(
    // Properties specific to your ApiKey implementation
    MyApiKey.Property.PROP11,
    MyApiKey.Property.Prop2(count = 10),
    ),
    daysUntilExpiry = 30,
    extras = setOf("something"),
    )

    println(apiKey)
    // Data: [
    // keyId: com.example
    // uuid: ZNJ2M-A2BNV-QMZRW-7FYDH-8C9HQ-1NY3N-W7TAG-020XM-TA3ZC
    // properties: [
    // PROP11
    // PROP2:10
    // ]
    // extras: [
    // something
    // ]
    // createdAt: 2023-07-09T10:43:57.866Z
    // expiresAt: 2023-08-08T10:43:57.866Z
    // ]
    // Signature: [
    // r8p+mcdd/NTaeSWvhcc4KkZlclL4mU7bEdPSwH4s+9SpYYftiTTqHu8bK86O5Eug
    // sQuNTH0lScJeOqmTKBmKnWN3YbKeox6uPCap/hNaf5LnibnpaTCWpC0Ah0JAue0Q
    // au/WuID5MKojD5PEmKbnknOqhwGMImeNzfTqLbcFllh6FxroYi+n79xDTGlnhe41
    // 6/dDy2yZFXYTVHiHZBk0gCnqoJmNFULvmoB+DS1zormtByHYn03lxXOnnNSm21XD
    // N3G3Ofe8bFEQgEFMcDLJNKwkcMS0nRbVoxQozt3n02IJUXTLd+RuBR+zF8VYboso
    // 9LW0IVko5PcbiYPpIzuB8Q==
    // ]

    val encodedApiKey = apiKey.encode()

    println(encodedApiKey)
    // a2V5SWQ6IGNvbS5leGFtcGxlCnV1aWQ6IFpOSjJNLUEyQk5WLVFNWlJXLTdGWURI
    // LThDOUhRLTFOWTNOLVc3VEFHLTAyMFhNLVRBM1pDCnByb3BlcnRpZXM6IFsKICAg
    // IFBST1AxMQogICAgUFJPUDI6MTAKXQpleHRyYXM6IFsKICAgIHNvbWV0aGluZwpd
    // CmNyZWF0ZWRBdDogMjAyMy0wNy0wOVQxMDo0Mzo1Ny44NjZaCmV4cGlyZXNBdDog
    // MjAyMy0wOC0wOFQxMDo0Mzo1Ny44NjZa:r8p+mcdd/NTaeSWvhcc4KkZlclL4mU7
    // bEdPSwH4s+9SpYYftiTTqHu8bK86O5EugsQuNTH0lScJeOqmTKBmKnWN3YbKeox6
    // uPCap/hNaf5LnibnpaTCWpC0Ah0JAue0Qau/WuID5MKojD5PEmKbnknOqhwGMIme
    // NzfTqLbcFllh6FxroYi+n79xDTGlnhe416/dDy2yZFXYTVHiHZBk0gCnqoJmNFUL
    // vmoB+DS1zormtByHYn03lxXOnnNSm21XDN3G3Ofe8bFEQgEFMcDLJNKwkcMS0nRb
    // VoxQozt3n02IJUXTLd+RuBR+zF8VYboso9LW0IVko5PcbiYPpIzuB8Q==

    // The below output is what actually is signed using the RSA
    // PrivateKey. The below string value is encoded to bytes, then
    // the SHA3-256 hash value of it is signed using signature
    // algorithm NONEwithRSA.
    //
    // Not necessary for consumers to know about or use
    // by any means, but depicts capabilities for ApiKey issuers.
    println(ApiKey.Data.decodeToString(encodedApiKey))
    // keyId: com.example
    // uuid: ZNJ2M-A2BNV-QMZRW-7FYDH-8C9HQ-1NY3N-W7TAG-020XM-TA3ZC
    // properties: [
    // PROP11
    // PROP2:10
    // ]
    // extras: [
    // something
    // ]
    // createdAt: 2023-07-09T10:43:57.866Z
    // expiresAt: 2023-08-08T10:43:57.866Z
    }
  • Checking for validity:

    fun main() {

    // Verify previously encoded api key using signer's RSA public key.
    // Upon successful verification, the Data will be reconstructed.
    val verifiedApiKey = try {
    MyApiKey.of(encodedApiKey)
    } catch (e: Exception) {
    // In the event the encodedApiKey was modified (either the data
    // or signature bytes), verification will fail.
    throw e
    }

    println(verifiedApiKey.data.keyId)
    // com.example
    println(verifiedApiKey.data.isExpired())
    // false
    }

Packages

Link copied to clipboard
common