fix: Authenticate all Open Group API calls

* Use unblinded authentication when we have `capabilities` data for the open group server we are sending the request to but don't have the `blind` capability
* Use blinded authentication when we haven't gotten any `capabilities` for an open group server, or if we have `capabilities` and the server has the `blind` capability
This commit is contained in:
charles 2022-10-25 16:25:06 +11:00
parent 1dbcffe40b
commit 31d388e00b
2 changed files with 68 additions and 74 deletions

View File

@ -41,7 +41,7 @@ class BackgroundGroupAddJob(val joinUrl: String): Job {
} }
// get image // get image
storage.setOpenGroupPublicKey(openGroup.server, openGroup.serverPublicKey) storage.setOpenGroupPublicKey(openGroup.server, openGroup.serverPublicKey)
val (capabilities, info) = OpenGroupApi.getCapabilitiesAndRoomInfo(openGroup.room, openGroup.server, false).get() val (capabilities, info) = OpenGroupApi.getCapabilitiesAndRoomInfo(openGroup.room, openGroup.server).get()
storage.setServerCapabilities(openGroup.server, capabilities.capabilities) storage.setServerCapabilities(openGroup.server, capabilities.capabilities)
val imageId = info.imageId val imageId = info.imageId
storage.addOpenGroup(openGroup.joinUrl()) storage.addOpenGroup(openGroup.joinUrl())

View File

@ -255,7 +255,6 @@ object OpenGroupApi {
val queryParameters: Map<String, String> = mapOf(), val queryParameters: Map<String, String> = mapOf(),
val parameters: Any? = null, val parameters: Any? = null,
val headers: Map<String, String> = mapOf(), val headers: Map<String, String> = mapOf(),
val isAuthRequired: Boolean = true,
val body: ByteArray? = null, val body: ByteArray? = null,
/** /**
* Always `true` under normal circumstances. You might want to disable * Always `true` under normal circumstances. You might want to disable
@ -301,73 +300,71 @@ object OpenGroupApi {
?: return Promise.ofFail(Error.NoEd25519KeyPair) ?: return Promise.ofFail(Error.NoEd25519KeyPair)
val urlRequest = urlBuilder.toString() val urlRequest = urlBuilder.toString()
val headers = request.headers.toMutableMap() val headers = request.headers.toMutableMap()
if (request.isAuthRequired) { val nonce = sodium.nonce(16)
val nonce = sodium.nonce(16) val timestamp = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())
val timestamp = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) var pubKey = ""
var pubKey = "" var signature = ByteArray(Sign.BYTES)
var signature = ByteArray(Sign.BYTES) var bodyHash = ByteArray(0)
var bodyHash = ByteArray(0) if (request.parameters != null) {
if (request.parameters != null) { val parameterBytes = JsonUtil.toJson(request.parameters).toByteArray()
val parameterBytes = JsonUtil.toJson(request.parameters).toByteArray() val parameterHash = ByteArray(GenericHash.BYTES_MAX)
val parameterHash = ByteArray(GenericHash.BYTES_MAX) if (sodium.cryptoGenericHash(
if (sodium.cryptoGenericHash( parameterHash,
parameterHash, parameterHash.size,
parameterHash.size, parameterBytes,
parameterBytes, parameterBytes.size.toLong()
parameterBytes.size.toLong()
)
) {
bodyHash = parameterHash
}
} else if (request.body != null) {
val byteHash = ByteArray(GenericHash.BYTES_MAX)
if (sodium.cryptoGenericHash(
byteHash,
byteHash.size,
request.body,
request.body.size.toLong()
)
) {
bodyHash = byteHash
}
}
val messageBytes = Hex.fromStringCondensed(publicKey)
.plus(nonce)
.plus("$timestamp".toByteArray(Charsets.US_ASCII))
.plus(request.verb.rawValue.toByteArray())
.plus("/${request.endpoint.value}".toByteArray())
.plus(bodyHash)
if (serverCapabilities.contains(Capability.BLIND.name.lowercase())) {
SodiumUtilities.blindedKeyPair(publicKey, ed25519KeyPair)?.let { keyPair ->
pubKey = SessionId(
IdPrefix.BLINDED,
keyPair.publicKey.asBytes
).hexString
signature = SodiumUtilities.sogsSignature(
messageBytes,
ed25519KeyPair.secretKey.asBytes,
keyPair.secretKey.asBytes,
keyPair.publicKey.asBytes
) ?: return Promise.ofFail(Error.SigningFailed)
} ?: return Promise.ofFail(Error.SigningFailed)
} else {
pubKey = SessionId(
IdPrefix.UN_BLINDED,
ed25519KeyPair.publicKey.asBytes
).hexString
sodium.cryptoSignDetached(
signature,
messageBytes,
messageBytes.size.toLong(),
ed25519KeyPair.secretKey.asBytes
) )
) {
bodyHash = parameterHash
}
} else if (request.body != null) {
val byteHash = ByteArray(GenericHash.BYTES_MAX)
if (sodium.cryptoGenericHash(
byteHash,
byteHash.size,
request.body,
request.body.size.toLong()
)
) {
bodyHash = byteHash
} }
headers["X-SOGS-Nonce"] = encodeBytes(nonce)
headers["X-SOGS-Timestamp"] = "$timestamp"
headers["X-SOGS-Pubkey"] = pubKey
headers["X-SOGS-Signature"] = encodeBytes(signature)
} }
val messageBytes = Hex.fromStringCondensed(publicKey)
.plus(nonce)
.plus("$timestamp".toByteArray(Charsets.US_ASCII))
.plus(request.verb.rawValue.toByteArray())
.plus("/${request.endpoint.value}".toByteArray())
.plus(bodyHash)
if (serverCapabilities.isEmpty() || serverCapabilities.contains(Capability.BLIND.name.lowercase())) {
SodiumUtilities.blindedKeyPair(publicKey, ed25519KeyPair)?.let { keyPair ->
pubKey = SessionId(
IdPrefix.BLINDED,
keyPair.publicKey.asBytes
).hexString
signature = SodiumUtilities.sogsSignature(
messageBytes,
ed25519KeyPair.secretKey.asBytes,
keyPair.secretKey.asBytes,
keyPair.publicKey.asBytes
) ?: return Promise.ofFail(Error.SigningFailed)
} ?: return Promise.ofFail(Error.SigningFailed)
} else {
pubKey = SessionId(
IdPrefix.UN_BLINDED,
ed25519KeyPair.publicKey.asBytes
).hexString
sodium.cryptoSignDetached(
signature,
messageBytes,
messageBytes.size.toLong(),
ed25519KeyPair.secretKey.asBytes
)
}
headers["X-SOGS-Nonce"] = encodeBytes(nonce)
headers["X-SOGS-Timestamp"] = "$timestamp"
headers["X-SOGS-Pubkey"] = pubKey
headers["X-SOGS-Signature"] = encodeBytes(signature)
val requestBuilder = okhttp3.Request.Builder() val requestBuilder = okhttp3.Request.Builder()
.url(urlRequest) .url(urlRequest)
@ -794,16 +791,14 @@ object OpenGroupApi {
private fun sequentialBatch( private fun sequentialBatch(
server: String, server: String,
requests: MutableList<BatchRequestInfo<*>>, requests: MutableList<BatchRequestInfo<*>>
authRequired: Boolean = true
): Promise<List<BatchResponse<*>>, Exception> { ): Promise<List<BatchResponse<*>>, Exception> {
val request = Request( val request = Request(
verb = POST, verb = POST,
room = null, room = null,
server = server, server = server,
endpoint = Endpoint.Sequence, endpoint = Endpoint.Sequence,
parameters = requests.map { it.request }, parameters = requests.map { it.request }
isAuthRequired = authRequired
) )
return getBatchResponseJson(request, requests) return getBatchResponseJson(request, requests)
} }
@ -904,7 +899,7 @@ object OpenGroupApi {
} }
fun getCapabilities(server: String): Promise<Capabilities, Exception> { fun getCapabilities(server: String): Promise<Capabilities, Exception> {
val request = Request(verb = GET, room = null, server = server, endpoint = Endpoint.Capabilities, isAuthRequired = false) val request = Request(verb = GET, room = null, server = server, endpoint = Endpoint.Capabilities)
return getResponseBody(request).map { response -> return getResponseBody(request).map { response ->
JsonUtil.fromJson(response, Capabilities::class.java) JsonUtil.fromJson(response, Capabilities::class.java)
} }
@ -912,8 +907,7 @@ object OpenGroupApi {
fun getCapabilitiesAndRoomInfo( fun getCapabilitiesAndRoomInfo(
room: String, room: String,
server: String, server: String
authRequired: Boolean = true
): Promise<Pair<Capabilities, RoomInfo>, Exception> { ): Promise<Pair<Capabilities, RoomInfo>, Exception> {
val requests = mutableListOf<BatchRequestInfo<*>>( val requests = mutableListOf<BatchRequestInfo<*>>(
BatchRequestInfo( BatchRequestInfo(
@ -933,7 +927,7 @@ object OpenGroupApi {
responseType = object : TypeReference<RoomInfo>(){} responseType = object : TypeReference<RoomInfo>(){}
) )
) )
return sequentialBatch(server, requests, authRequired).map { return sequentialBatch(server, requests).map {
val capabilities = it.firstOrNull()?.body as? Capabilities ?: throw Error.ParsingFailed val capabilities = it.firstOrNull()?.body as? Capabilities ?: throw Error.ParsingFailed
val roomInfo = it.lastOrNull()?.body as? RoomInfo ?: throw Error.ParsingFailed val roomInfo = it.lastOrNull()?.body as? RoomInfo ?: throw Error.ParsingFailed
capabilities to roomInfo capabilities to roomInfo