Skip to content

Commit

Permalink
Merge pull request #58 from tw-mosip/update-API-def
Browse files Browse the repository at this point in the history
Update Tuvali API definitions (Move away from Google Nearby API definitions)
  • Loading branch information
vharsh authored Jun 12, 2023
2 parents 0955769 + 0c91a56 commit e3892d9
Show file tree
Hide file tree
Showing 60 changed files with 1,023 additions and 39,057 deletions.
137 changes: 82 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@

> Warning: The library is under active development and no major version is released yet. Please anticipate non backward compatible changes to the API and functional behavior in the upcoming releases.
> Warning: The library is under active development and no major version is released yet. Please anticipate non-backward compatible changes to the API and functional behavior in the upcoming releases.
# Tuvali - React native module library
This is the react native module for the [OpenID for Verifiable Presentations over BLE](https://tlodderstedt.github.io/openid-for-verifiable-presentations-offline-1_0-00.html) implementation to support sending vc/vp using Bluetooth Low Energy local channel.
This is the React native module for the [OpenID for Verifiable Presentations over BLE](https://tlodderstedt.github.io/openid-for-verifiable-presentations-offline-1_0-00.html) implementation to support sending vc/vp using Bluetooth Low Energy local channel.

This contains the source code for the ios, android modules as well as a sample app under `example/` folder. The sample app can be used for testing the modules being worked on in case it is needed.

Expand All @@ -19,98 +19,125 @@ npm install mosip/tuvali#v0.3.7
```

# API documentation
Firstly, for establishing the secured connection over BLE the connection params which include `cid` and `Public key` needs to be exchanged between two devices. The exchange of parameters can be accomplished, but is not limited to, by using a QR code.
Firstly, for establishing the secured connection over BLE the Verifier's URI needs to be exchanged between two devices. The exchange of URI can be accomplished, but is not limited to, by using a QR code.

For example use QR code generator to visually display params and QR code scanner to get params. A mobile app that displays a QR code can act as an `advertiser` by including its connection params as data in the QR code and another device can act as `discoverer` which scans the QR code, it can extract the connection params and initiate a BLE connection with the advertising device.
For example use QR code generator to visually display URI and QR code scanner to read. A mobile app that displays a QR code can act as an `Verifier` by including its URI as data in the QR code and another device can act as `Wallet` which scans the QR code, it can extract the URI and initiate a BLE connection with the advertising device.

> Note: The terms `advertiser` and `discoverer` are used for legacy API compatibility reasons. These terms will be replaced with `Verifier` and `Wallet` soon. Hence the terms are used interchangeably as required.
## URI exchange and Establishing connection

## Connection parameters exchange
The device on which the QR code is displayed shall generate connection parameters using getConnectionParameters() method:
### Verifier
The Verifier device can show a QR code with the URI. Verifier can generate URI through startAdvertisement() method. Once advertisement is started, Verifier will keep advertising with an advertisement payload derived from URI.

```typescript
import OpenIdBle from 'react-native-openid4vp-ble';
const { Openid4vpBle } = OpenIdBle;
const { verifier } = OpenIdBle;

const params = Openid4vpBle.getConnectionParameters();
console.log(params);
const uri = verifier.startAdvertisement();
console.log(uri);
```

The device that scans the QR code will extract the connection parameters from QR code and set its connection parameters using setConnectionParameters() method :
The URI contains:

```typescript
Openid4vpBle.setConnectionParameters(params);
```
The connection params contains:

```json
{
"cid": "ilB8l",
"pk": "4f56504d4f5349505f66b067c008A4484AEC5A769CED2307F59E43DC81A3F768"
}
OPENID4VP://connect?name=STADONENTRY&key=8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a
```
The `pk` part of the data is the same data that will be advertised by the `advertiser` device but in hex encoded form.
E.g: OVPMOSIP_<first 5 bytes of public key>

Note: The "cid" parameter is there for legacy reasons and will be removed soon.
URI structure can be found in the spec --> https://bitbucket.org/openid/connect/src/master/openid-4-verifiable-presentations-over-ble/openid-4-verifiable-presentations-over-ble-1_0.md

## Establising connection
E.g: OPENID4VP://connect?name=<CLIENT_NAME>&key=<VERIFIER_PUBLIC_KEY>

The device that displays the QR code will become `advertiser` and waits for a connection:

### Wallet
The Wallet device that scans the QR code will extract the URI from QR code and start scanning using startConnection() method.

```typescript
Openid4vpBle.createConnection('advertiser', () => {
// Add the code that needs to run when bluetooth advertisement started successfully
});
wallet.startConnection(uri);
```

and the other device that scans the QR code will become `discoverer` and will attempt to discover the devices:
The Wallet device will keep on scanning for a verifier that has same URI in its advertisement. If URI is matched, Wallet will initiate a connection with the Verifier and exchange Public Keys.

## Share data

Once the connection is established, Wallet can send the data by:

```typescript
Openid4vpBle.createConnection('discoverer', () => {
// Add the code that needs to run when bluetooth scan started successfully
});
wallet.send(data);
```

## Share data
Wallet will start sending data in a secured way to the Verifier.

Once the connection is established, either app can send the data:
Note: At this moment, we currently support data transfer from Wallet to Verifier only.

## Verifier Response

Once Data is received, Verifier can send verification status by:

```typescript
Openid4vpBle.send(message, () => {
// Add the code that needs to run once data is shared successfully
});
verifier.sendVerificationStatus(status);
```

Following sequence of actions should be performed to transfer data over BLE
1. Exchange Wallet public key
Discoverer calls `Openid4vpBle.send` with message type "exchange-sender-info". The callback passed is executed on successful writing wallet public key to Identify characteristic
2. Send VC data
At the moment, only VC data is being exchanged from Wallet to Verifier instead of VP response mentioned in the specification. Hence `Openid4vpBle.send` should be called message type "send-vc" for sending VC data from Wallet to Verifier.
3. Send VC response
Verifier can exchange "Accept/Reject" status to Wallet with following message type for `Openid4vpBle.send` method
- For Accept status, message type "send-vc:response\n1"
- For Reject status, message type "send-vc:response\n2"
Status can be either `ACCEPTED` or `REJECTED`. Sending verification status acts as closure for transmission and devices will start disconnecting.


## Events from Tuvali

Tuvali sends multiple events to propagate connection status, received data etc. These events can be subscribed to by calling:

on Wallet:

```typescript
wallet.handleDataEvents((event: WalletDataEvent) => {
// Add the code that needs to run once data is received
})
```

on Verifier:

Data received from other app via BLE can be subscribed to using:
```typescript
Openid4vpBle.handleNearbyEvents((event) => {
verifier.handleDataEvents((event: VerifierDataEvent) => {
// Add the code that needs to run once data is received
})
```


Here are the different types of events that can be received
1. Advertiser gets an event when Identify charactertistic value is received on Verifier side. The format of the event is
`{"type": "msg", "data": "exchange-sender-info\n{\"deviceName\": \"Wallet\"}}`
2. Advertiser gets an event when VC data is received. The format of the event is `{"type": "msg", "data": "send-vc\n<Entire VC data>}`
3. Both Advertiser and Discoverer can receive disconnected event in the following format `{"type": "onDisconnected"}`
4. Both Advertiser and Discoverer can receive error event in the following format `{"type": "onError", "message": "Something went wrong in BLE: ${e.cause}"}`

### Common Events
Events which are emitted by both Wallet and Verifier

1. onConnected
* `{"type": "onConnected"}`
* on BLE connection getting established between Wallet and Verifier
2. onSecureChannelEstablished
* `{"type": "onSecureChannelEstablished"}`
* on completion of key exchange between Wallet and Verifier
3. onError
* `{"type": "onError", "message": "Something Went wrong in BLE", "code": "TVW_CON_001"}`
* on any error in Wallet or Verifier
4. onDisconnected
* `{"type": "onDisconnected"}`
* on BLE disconnection between Wallet and Verifier


### Wallet Specific Events

1. onDataSent
* `{"type": "onDataSent"}`
* on completion of Data transfer from the Wallet Side
2. onVerificationStatusReceived
* `{"type": "onVerificationStatusReceived", "status": "ACCEPTED"}`
* on received verification status from Verifier

### Verifier Specific Events

1. onDataReceived
* `{"type": "onDataReceived"}`
* on receiving data from the Wallet Side

## Connection closure

The device on which app is running can destroy the connection by calling destroyConnection() method:
The device on which app is running can destroy the connection by calling disconnect() method:

```typescript
Openid4vpBle.destroyConnection();
wallet/verifier.disconnect();
```
4 changes: 2 additions & 2 deletions android/src/main/java/io/mosip/tuvali/ble/central/Central.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ class Central(context: Context, centralLister: ICentralListener) {
messageSender.sendMessage(DisconnectAndCloseMessage())
}

fun scan(serviceUuid: UUID, advIdentifier: String) {
val scanStartMessage = ScanStartMessage(serviceUuid, advIdentifier)
fun scan(serviceUuid: UUID) {
val scanStartMessage = ScanStartMessage(serviceUuid)

messageSender.sendMessage(scanStartMessage)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ class Controller(val context: Context) {
scanner = Scanner(context)
scanner?.start(
scanStartMessage.serviceUUID,
scanStartMessage.advPayload,
this::onDeviceFound,
this::onScanStartFailure
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import io.mosip.tuvali.transfer.Util.Companion.getLogTag

class Scanner(context: Context) {
private val logTag = getLogTag(javaClass.simpleName)
private lateinit var advPayload: String
private lateinit var onScanStartFailure: (Int) -> Unit
private lateinit var onDeviceFound: (ScanResult) -> Unit
private var bluetoothLeScanner: BluetoothLeScanner
Expand All @@ -38,13 +37,11 @@ class Scanner(context: Context) {
@SuppressLint("MissingPermission")
fun start(
serviceUUID: UUID,
advPayload: String,
onDeviceFound: (ScanResult) -> Unit,
onScanStartFailure: (Int) -> Unit
) {
this.onDeviceFound = onDeviceFound;
this.onScanStartFailure = onScanStartFailure;
this.advPayload = advPayload;

val filter = ScanFilter.Builder()
.setServiceUuid(ParcelUuid(serviceUUID))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import io.mosip.tuvali.ble.central.ICentralListener
import io.mosip.tuvali.ble.central.state.message.*
import io.mosip.tuvali.ble.exception.CentralStateHandlerException
import io.mosip.tuvali.transfer.Util.Companion.getLogTag
import org.bouncycastle.util.encoders.Hex

class StateHandler(
looper: Looper,
Expand Down Expand Up @@ -223,7 +224,7 @@ class StateHandler(
}
IMessage.CentralStates.NOTIFICATION_RECEIVED.ordinal -> {
val notificationReceivedMessage = msg.obj as NotificationReceivedMessage
Log.d(logTag, "Received notification from ${notificationReceivedMessage.charUUID} with value: ${notificationReceivedMessage.value}")
Log.d(logTag, "Received notification from ${notificationReceivedMessage.charUUID} with value: ${Hex.toHexString(notificationReceivedMessage.value)}")

listener.onNotificationReceived(notificationReceivedMessage.charUUID, notificationReceivedMessage.value)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ package io.mosip.tuvali.ble.central.state.message

import java.util.*

class ScanStartMessage(val serviceUUID: UUID, val advPayload: String): IMessage(
class ScanStartMessage(val serviceUUID: UUID): IMessage(
CentralStates.SCAN_START
)
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,8 @@ class Peripheral(context: Context, peripheralListener: IPeripheralListener) {
private fun disconnectAndClose(delay: Long) {
messageSender.sendMessageDelayed(DisconnectAndCloseMessage(), delay)
}

fun isAdvertisementStarted(): Boolean {
return messageSender.getCurrentState() > StateHandler.States.Advertising
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.mosip.tuvali.common.advertisementPayload

import org.bouncycastle.util.encoders.Hex

class AdvertisementPayload {
companion object {
fun getAdvPayload(identifier: String, key: ByteArray): ByteArray {
return identifier.toByteArray() + "_".toByteArray() + key.copyOfRange(0, 5)
}

fun getAdvPayload(identifier: String, hexKey: String): ByteArray {
val key = Hex.decode(hexKey)
return getAdvPayload(identifier, key)
}

fun getScanRespPayload(key: ByteArray): ByteArray {
return key.copyOfRange(5, 32)
}
}
}
35 changes: 35 additions & 0 deletions android/src/main/java/io/mosip/tuvali/common/uri/openId4vpURI.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.mosip.tuvali.common.uri

import android.net.Uri
import android.text.TextUtils.isEmpty

private const val URI_IDENTIFIER = "OPENID4VP"
private const val NAME_QUERY_PARAM_NAME = "name"
private const val KEY_QUERY_PARAM_NAME = "key"

class OpenId4vpURI {
private var uri: Uri

constructor(name: String, hexPK: String) {
this.uri = Uri.Builder()
.scheme("$URI_IDENTIFIER://connect")
.appendQueryParameter(NAME_QUERY_PARAM_NAME, name)
.appendQueryParameter(KEY_QUERY_PARAM_NAME, hexPK).build()
}

constructor(uri: String) {
try {
this.uri = Uri.parse(uri)
} catch (e: Exception) {
this.uri = Uri.EMPTY
}
}

fun getName() = uri.getQueryParameter(NAME_QUERY_PARAM_NAME).orEmpty()

fun getHexPK() = uri.getQueryParameter(KEY_QUERY_PARAM_NAME).orEmpty()

fun isValid() = !(isEmpty(getName()) || isEmpty(getHexPK()))

override fun toString() = uri.toString()
}
5 changes: 3 additions & 2 deletions android/src/main/java/io/mosip/tuvali/exception/ErrorCode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package io.mosip.tuvali.exception
// Component+ROLE --> TVW(Tuvali+Wallet) | TVV(Tuvali+Verifier) | TUV(Tuvali where role is unknown)
// UNK --> If stage is not known

enum class ErrorCode(val code: String) {
enum class ErrorCode(val value: String) {
UnknownException("TUV_UNK_001"),

WalletUnknownException("TVW_UNK_001"),
Expand All @@ -17,7 +17,8 @@ enum class ErrorCode(val code: String) {
PeripheralStateHandlerException("TVV_UNK_002"),
VerifierTransferHandlerException("TVV_UNK_003"),

MTUNegotiationException("TVW_CON_001"),
InvalidURIException("TVW_CON_001"),
MTUNegotiationException("TVW_CON_002"),
//TODO: Create specific error codes for the below exception
TransferFailedException("TVW_REP_001"),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import io.mosip.tuvali.transfer.Util
import io.mosip.tuvali.verifier.exception.VerifierException
import io.mosip.tuvali.wallet.exception.WalletException

class OpenIdBLEExceptionHandler(private val sendError: (String, ErrorCode) -> Unit, private val stopBle: (Callback) -> Unit) {
class OpenIdBLEExceptionHandler(private val sendError: (String, ErrorCode) -> Unit, private val stopBle: (() -> Unit) -> Unit) {
private val logTag = Util.getLogTag(javaClass.simpleName)
private var walletExceptionHandler: WalletExceptionHandler = WalletExceptionHandler(sendError)
private var verifierExceptionHandler: VerifierExceptionHandler = VerifierExceptionHandler(sendError)
Expand All @@ -37,10 +37,10 @@ class OpenIdBLEExceptionHandler(private val sendError: (String, ErrorCode) -> Un
}
}

try{
try {
stopBle {}
} catch (e: Exception) {
Log.d(logTag,"Failed to stop BLE connection while handling exception: $e")
Log.d(logTag, "Failed to stop BLE connection while handling exception: $e")
}
}

Expand Down
Loading

0 comments on commit e3892d9

Please sign in to comment.