api-key
TODO
Benefits for use with Inter-Process Communication (IPC)
By including the
applicationIdin the API key data (viakeyId), the key can only be utilized for that application.Mitigation of MITM attacks, as the validator application communicates only with the
applicationIdspecified 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
ApiKeyclass/**
* 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
"""
}
}Content copied to clipboardCreate 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
}Content copied to clipboardChecking 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
}Content copied to clipboard