org.http4k.security.digest.DigestAuthProvider.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of http4k-security-digest Show documentation
Show all versions of http4k-security-digest Show documentation
Http4k Security Digest support
package org.http4k.security.digest
import org.http4k.core.Method
import org.http4k.core.Request
import org.http4k.core.Response
import org.http4k.core.Status.Companion.UNAUTHORIZED
import org.http4k.security.NonceGenerator
import org.http4k.security.NonceVerifier
import java.security.MessageDigest
/**
* For use in servers. Verifies digest credentials and generates challenge responses
*
* TODO add support for opaque in challenge. Unknown if it needs to be verified, or how it should be generated (i.e. static, user-specific, etc.)
* The IOT device I used for testing constantly returned the same opaque
*/
enum class DigestMode(val authHeaderName: String, val challengeHeaderName: String) {
Standard("Authorization", "WWW-Authenticate"),
Proxy("Proxy-Authorization", "Proxy-Authenticate")
}
class DigestAuthProvider(
private val realm: String,
private val passwordLookup: (String) -> String?,
private val qop: List,
private val algorithm: String,
private val nonceGenerator: NonceGenerator,
private val nonceVerifier: NonceVerifier = { true },
private val digestMode: DigestMode = DigestMode.Standard
) {
fun digestCredentials(request: Request): DigestCredential? {
return request
.header(digestMode.authHeaderName)
?.let { DigestCredential.fromHeader(it) }
}
fun verify(credentials: DigestCredential, method: Method): Boolean {
val digestEncoder = DigestEncoder(MessageDigest.getInstance("MD5"))
// verify credentials pertain to this provider
if (credentials.algorithm != null && credentials.algorithm != algorithm) return false
if (credentials.realm != realm) return false
if (!nonceVerifier(credentials.nonce)) return false
if (qop.isNotEmpty() && (credentials.qop == null || credentials.qop !in qop)) return false
if (qop.isEmpty() && credentials.qop != null) return false
// verify credentials digest matches expected digest
val password = passwordLookup(credentials.username) ?: return false
val expectedDigest = digestEncoder(
method = method,
realm = realm,
qop = credentials.qop,
username = credentials.username,
password = password,
nonce = credentials.nonce,
cnonce = credentials.cnonce,
nonceCount = credentials.nonceCount,
digestUri = credentials.digestUri
)
val incomingDigest = credentials.responseBytes()
return MessageDigest.isEqual(incomingDigest, expectedDigest)
}
fun generateChallenge(): Response {
val header = DigestChallenge(
realm = realm,
nonce = nonceGenerator(),
algorithm = algorithm,
qop = qop,
opaque = null
)
return Response(UNAUTHORIZED).header(digestMode.challengeHeaderName, header.toHeaderValue())
}
}