initial commit

This commit is contained in:
Dmitry Novotochinov 2018-11-22 19:49:00 +03:00
commit 2ac837a4c8
No known key found for this signature in database
GPG Key ID: 43D1DAF5AD39C927
15 changed files with 860 additions and 0 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.pbxproj -text

46
.gitignore vendored Normal file
View File

@ -0,0 +1,46 @@
# OSX
#
.DS_Store
# node.js
#
node_modules/
npm-debug.log
yarn-error.log
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
project.xcworkspace
# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
# BUCK
buck-out/
\.buckd/
*.keystore

53
README.md Normal file
View File

@ -0,0 +1,53 @@
# react-native-status-keycard
## Getting started
`$ npm install react-native-status-keycard --save`
### Mostly automatic installation
`$ react-native link react-native-status-keycard`
### Manual installation
#### iOS
1. In XCode, in the project navigator, right click `Libraries``Add Files to [your project's name]`
2. Go to `node_modules``react-native-status-keycard` and add `RNStatusKeycard.xcodeproj`
3. In XCode, in the project navigator, select your project. Add `libRNStatusKeycard.a` to your project's `Build Phases``Link Binary With Libraries`
4. Run your project (`Cmd+R`)<
#### Android
1. Open up `android/app/src/main/java/[...]/MainActivity.java`
- Add `import com.reactlibrary.RNStatusKeycardPackage;` to the imports at the top of the file
- Add `new RNStatusKeycardPackage()` to the list returned by the `getPackages()` method
2. Append the following lines to `android/settings.gradle`:
```
include ':react-native-status-keycard'
project(':react-native-status-keycard').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-status-keycard/android')
```
3. Insert the following lines inside the dependencies block in `android/app/build.gradle`:
```
compile project(':react-native-status-keycard')
```
#### Windows
[Read it! :D](https://github.com/ReactWindows/react-native)
1. In Visual Studio add the `RNStatusKeycard.sln` in `node_modules/react-native-status-keycard/windows/RNStatusKeycard.sln` folder to their solution, reference from their app.
2. Open up your `MainPage.cs` app
- Add `using Status.Keycard.RNStatusKeycard;` to the usings at the top of the file
- Add `new RNStatusKeycardPackage()` to the `List<IReactPackage>` returned by the `Packages` method
## Usage
```javascript
import RNStatusKeycard from 'react-native-status-keycard';
// TODO: What to do with the module?
RNStatusKeycard;
```

43
android/build.gradle Normal file
View File

@ -0,0 +1,43 @@
buildscript {
repositories {
google()
maven {
url 'https://maven.google.com'
}
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
}
}
apply plugin: 'com.android.library'
android {
compileSdkVersion 26
defaultConfig {
minSdkVersion 18
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
}
repositories {
google()
mavenCentral()
maven {
url 'https://maven.google.com'
}
maven { url "https://jitpack.io" }
maven {
url "$rootDir/../node_modules/react-native/android"
}
}
dependencies {
implementation 'com.facebook.react:react-native:+'
implementation 'com.github.status-im:hardwallet-lite-android:3cad212'
}

Binary file not shown.

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

172
android/gradlew vendored Normal file
View File

@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
android/gradlew.bat vendored Normal file
View File

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -0,0 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.reactlibrary">
</manifest>

View File

@ -0,0 +1,128 @@
package im.status.ethereum.keycard;
import android.app.Activity;
import android.content.Intent;
import android.provider.Settings;
import android.util.Log;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.WritableMap;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import im.status.hardwallet_lite_android.io.APDUException;
public class RNStatusKeycardModule extends ReactContextBaseJavaModule implements LifecycleEventListener {
private static final String TAG = "StatusKeycard";
private SmartCard smartCard;
private final ReactApplicationContext reactContext;
public RNStatusKeycardModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
reactContext.addLifecycleEventListener(this);
}
@Override
public String getName() {
return "RNStatusKeycard";
}
@Override
public void onHostResume() {
Log.d("ONHOSTRESUme", " " + this.smartCard);
if (this.smartCard == null) {
this.smartCard = new SmartCard(getCurrentActivity(), reactContext);
}
}
@Override
public void onHostPause() {
}
@Override
public void onHostDestroy() {
}
@ReactMethod
public void nfcIsSupported(final Callback callback) {
Log.d("SC", " " + this.smartCard);
callback.invoke(smartCard.isNfcSupported());
}
@ReactMethod
public void nfcIsEnabled(final Callback callback) {
callback.invoke(smartCard.isNfcEnabled());
}
@ReactMethod
public void openNfcSettings(final Callback callback) {
Activity currentActivity = getCurrentActivity();
currentActivity.startActivity(new Intent(Settings.ACTION_NFC_SETTINGS));
callback.invoke();
}
@ReactMethod
public void start() {
smartCard.start();
}
@ReactMethod
public void init(final Callback successCallback, final Callback errorCallback) {
try {
SmartCardSecrets s = smartCard.init();
WritableMap params = Arguments.createMap();
params.putString("pin", s.getPin());
params.putString("puk", s.getPuk());
params.putString("password", s.getPairingPassword());
successCallback.invoke(params);
} catch (IOException | APDUException | NoSuchAlgorithmException | InvalidKeySpecException e) {
Log.d(TAG, e.getMessage());
errorCallback.invoke(e.getMessage());
}
}
@ReactMethod
public void pair(final String password, final Callback successCallback, final Callback errorCallback) {
try {
String pairing = smartCard.pair(password);
Log.d(TAG, "pairing done");
successCallback.invoke(pairing);
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
errorCallback.invoke(e.getMessage());
}
}
@ReactMethod
public void generateMnemonic(final String password, Callback successCallback, Callback errorCallback) {
try {
successCallback.invoke(smartCard.generateMnemonic(password));
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
errorCallback.invoke(e.getMessage());
}
}
@ReactMethod
public void saveMnemonic(final String mnemonic, final String password, String pin, Callback successCallback, Callback errorCallback) {
try {
smartCard.saveMnemonic(mnemonic, password, pin);
successCallback.invoke();
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
errorCallback.invoke(e.getMessage());
}
}
}

View File

@ -0,0 +1,29 @@
package im.status.ethereum.keycard;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.bridge.JavaScriptModule;
public class RNStatusKeycardPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(new RNStatusKeycardModule(reactContext));
}
// Deprecated from RN 0.47
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

View File

@ -0,0 +1,184 @@
package im.status.ethereum.keycard;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.nfc.NfcAdapter;
import android.support.annotation.Nullable;
import android.util.Log;
import com.facebook.react.bridge.*;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import im.status.hardwallet_lite_android.io.APDUException;
import im.status.hardwallet_lite_android.io.CardChannel;
import im.status.hardwallet_lite_android.io.CardListener;
import im.status.hardwallet_lite_android.io.CardManager;
import im.status.hardwallet_lite_android.wallet.Mnemonic;
import im.status.hardwallet_lite_android.wallet.WalletAppletCommandSet;
import im.status.hardwallet_lite_android.wallet.Pairing;
import im.status.hardwallet_lite_android.wallet.RecoverableSignature;
import im.status.hardwallet_lite_android.wallet.ApplicationInfo;
import im.status.hardwallet_lite_android.wallet.ApplicationStatus;
import im.status.hardwallet_lite_android.wallet.KeyPath;
import org.spongycastle.util.encoders.Hex;
public class SmartCard extends BroadcastReceiver implements CardListener {
private CardManager cardManager;
private Activity activity;
private ReactContext reactContext;
private NfcAdapter nfcAdapter;
private CardChannel cardChannel;
private static final String TAG = "SmartCard";
public SmartCard(Activity activity, ReactContext reactContext) {
this.cardManager = new CardManager();
this.cardManager.setCardListener(this);
this.activity = activity;
this.reactContext = reactContext;
this.nfcAdapter = NfcAdapter.getDefaultAdapter(activity.getBaseContext());
}
public String getName() {
return "SmartCard";
}
public void log(String s) {
Log.d(TAG, s);
}
public void start() {
this.cardManager.start();
if (this.nfcAdapter != null) {
IntentFilter filter = new IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED);
activity.registerReceiver(this, filter);
nfcAdapter.enableReaderMode(activity, this.cardManager, NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, null);
} else {
log("not support in this device");
}
}
@Override
public void onConnected(final CardChannel channel) {
this.cardChannel = channel;
sendEvent(reactContext, "keyCardOnConnected", null);
}
@Override
public void onDisconnected() {
sendEvent(reactContext, "keyCardOnDisconnected", null);
}
@Override
public void onReceive(Context context, Intent intent) {
final int state = intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE, NfcAdapter.STATE_OFF);
boolean on = false;
switch (state) {
case NfcAdapter.STATE_ON:
log("NFC ON");
case NfcAdapter.STATE_OFF:
log("NFC OFF");
default:
log("other");
}
}
public boolean isNfcSupported() {
return activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC);
}
public boolean isNfcEnabled() {
if (nfcAdapter != null) {
return nfcAdapter.isEnabled();
} else {
return false;
}
}
private void sendEvent(ReactContext reactContext,
String eventName,
@Nullable WritableMap params) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
public SmartCardSecrets init() throws IOException, APDUException, NoSuchAlgorithmException, InvalidKeySpecException {
WalletAppletCommandSet cmdSet = new WalletAppletCommandSet(this.cardChannel);
cmdSet.select().checkOK();
SmartCardSecrets s = SmartCardSecrets.generate();
cmdSet.init(s.getPin(), s.getPuk(), s.getPairingPassword()).checkOK();
return s;
}
public String pair(String pairingPassword) throws IOException, APDUException {
WalletAppletCommandSet cmdSet = new WalletAppletCommandSet(this.cardChannel);
log("Pairing password: " + pairingPassword);
Log.i(TAG, "Applet selection successful");
// First thing to do is selecting the applet on the card.
ApplicationInfo info = new ApplicationInfo(cmdSet.select().checkOK().getData());
Log.i(TAG, "Instance UID: " + Hex.toHexString(info.getInstanceUID()));
Log.i(TAG, "Secure channel public key: " + Hex.toHexString(info.getSecureChannelPubKey()));
Log.i(TAG, "Application version: " + info.getAppVersionString());
Log.i(TAG, "Free pairing slots: " + info.getFreePairingSlots());
cmdSet.autoPair(pairingPassword);
Pairing pairing = cmdSet.getPairing();
log("Pairing index: " + pairing.getPairingIndex());
log("Pairing key: " + Hex.toHexString(pairing.getPairingKey()));
log("Pairing toBase64: " + pairing.toBase64());
return pairing.toBase64();
}
public void unpair(String base64) throws IOException {
WalletAppletCommandSet cmdSet = new WalletAppletCommandSet(this.cardChannel);
Pairing pairing = new Pairing(base64);
cmdSet.setPairing(pairing);
cmdSet.autoUnpair();
}
public String generateMnemonic(String password) throws IOException, APDUException {
WalletAppletCommandSet cmdSet = new WalletAppletCommandSet(this.cardChannel);
cmdSet.select().checkOK();
cmdSet.autoPair(password);
cmdSet.autoOpenSecureChannel();
Log.i(TAG, "secure channel opened");
Mnemonic mnemonic = new Mnemonic(cmdSet.generateMnemonic(WalletAppletCommandSet.GENERATE_MNEMONIC_12_WORDS).checkOK().getData());
mnemonic.fetchBIP39EnglishWordlist();
return mnemonic.toMnemonicPhrase();
}
public void saveMnemonic(String mnemonic, String password, String pin) throws IOException, APDUException {
WalletAppletCommandSet cmdSet = new WalletAppletCommandSet(this.cardChannel);
cmdSet.select().checkOK();
cmdSet.autoPair(password);
cmdSet.autoOpenSecureChannel();
Log.i(TAG, "secure channel opened");
cmdSet.verifyPIN(pin).checkOK();
Log.i(TAG, "pin verified");
byte[] seed = Mnemonic.toBinarySeed(mnemonic, "");
cmdSet.loadKey(seed);
log("seed loaded to card");
}
}

View File

@ -0,0 +1,91 @@
package im.status.ethereum.keycard;
import android.support.annotation.NonNull;
import android.util.Base64;
import static android.util.Base64.NO_PADDING;
import java.security.SecureRandom;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
public class SmartCardSecrets {
public static long PIN_BOUND = 999999L;
public static long PUK_BOUND = 999999999999L;
private String pin;
private String puk;
private String pairingPassword;
private byte[] pairingToken;
public SmartCardSecrets(String pin, String puk, String pairingPassword, byte[] pairingToken) {
this.pin = pin;
this.puk = puk;
this.pairingPassword = pairingPassword;
this.pairingToken = pairingToken;
}
@NonNull
public static SmartCardSecrets generate() throws NoSuchAlgorithmException, InvalidKeySpecException {
String pairingPassword = randomToken(12);
byte[] pairingToken = generatePairingKey(pairingPassword.toCharArray());
long pinNumber = randomLong(PIN_BOUND);
long pukNumber = randomLong(PUK_BOUND);
String pin = String.format("%06d", pinNumber);
String puk = String.format("%012d", pukNumber);
return new SmartCardSecrets(pin, puk, pairingPassword, pairingToken);
}
public static SmartCardSecrets testSecrets() throws NoSuchAlgorithmException, InvalidKeySpecException {
String pairingPassword = "WalletAppletTest";
byte[] pairingToken = generatePairingKey(pairingPassword.toCharArray());
return new SmartCardSecrets("000000", "123456789012", pairingPassword, pairingToken);
}
public String getPin() {
return pin;
}
public String getPuk() {
return puk;
}
public String getPairingPassword() {
return pairingPassword;
}
public byte[] getPairingToken() {
return pairingToken;
}
public static byte[] generatePairingKey(char[] pairing) throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
String salt = "Status Hardware Wallet Lite";
PBEKeySpec spec = new PBEKeySpec(pairing, salt.getBytes(), 50000, 32*8);
SecretKey key = skf.generateSecret(spec);
return key.getEncoded();
}
public static long randomLong(long bound) {
SecureRandom random = new SecureRandom();
return Math.abs(random.nextLong()) % bound;
}
public static String randomToken(int length) {
return Base64.encodeToString(randomBytes(length), NO_PADDING);
}
public static byte[] randomBytes(int length) {
SecureRandom random = new SecureRandom();
byte data[] = new byte[length];
random.nextBytes(data);
return data;
}
}

6
index.js Normal file
View File

@ -0,0 +1,6 @@
import { NativeModules } from 'react-native';
const { RNStatusKeycard } = NativeModules;
export default RNStatusKeycard;

12
package.json Normal file
View File

@ -0,0 +1,12 @@
{
"name": "react-native-status-keycard",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": ""
}