├── .gitattributes ├── .github └── workflows │ └── npm-publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── RNSslPinning.podspec ├── android ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── toyberman │ ├── RNSslPinningModule.java │ ├── RNSslPinningPackage.java │ └── Utils │ ├── OkHttpUtils.java │ └── Utilities.java ├── index.d.ts ├── index.js ├── index.js.flow ├── ios ├── RNSslPinning.xcodeproj │ └── project.pbxproj ├── RNSslPinning.xcworkspace │ └── contents.xcworkspacedata └── RNSslPinning │ ├── RNSslPinning.h │ └── RNSslPinning.m └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: npm-publish 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | npm-publish: 8 | name: npm-publish 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout repository 12 | uses: actions/checkout@master 13 | - name: Set up Node.js 14 | uses: actions/setup-node@master 15 | with: 16 | node-version: 10.0.0 17 | - name: Publish if version has been updated 18 | uses: pascalgn/npm-publish-action@1.3.9 19 | with: # All of theses inputs are optional 20 | tag_name: "v%s" 21 | tag_message: "v%s" 22 | commit_pattern: "^Release (\\S+)" 23 | workspace: "." 24 | publish_command: "yarn" 25 | publish_args: "--non-interactive" 26 | env: # More info about the environment variables in the README 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Leave this as is, it's automatically generated 28 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} # You need to set this in your repo settings 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # OSX 3 | # 4 | .DS_Store 5 | 6 | # node.js 7 | # 8 | node_modules/ 9 | npm-debug.log 10 | yarn-error.log 11 | 12 | 13 | # Xcode 14 | # 15 | build/ 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | xcuserdata 25 | *.xccheckout 26 | *.moved-aside 27 | DerivedData 28 | *.hmap 29 | *.ipa 30 | *.xcuserstate 31 | project.xcworkspace 32 | 33 | 34 | # Android/IntelliJ 35 | # 36 | build/ 37 | .idea 38 | .gradle 39 | local.properties 40 | *.iml 41 | 42 | # BUCK 43 | buck-out/ 44 | \.buckd/ 45 | *.keystore 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-present, Maxim Toyberman. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # react-native-ssl-pinning 3 | 4 | React-Native ssl pinning & public key pinning using OkHttp 3 in Android, and AFNetworking on iOS. 5 | 6 | ## NOTES: 7 | 8 | - for RN 0.60.0 or later use `react-native-ssl-pinning@latest` 9 | 10 | 11 | ## Getting started 12 | 13 | `$ npm install react-native-ssl-pinning --save` 14 | 15 | 16 | ### Mostly automatic installation 17 | 18 | > If you are using `React Native 0.60.+` [the link should happen automatically](https://github.com/react-native-community/cli/blob/master/docs/autolinking.md). in iOS run pod install 19 | 20 | `$ react-native link react-native-ssl-pinning` 21 | 22 | ### Manual installation 23 | 24 | 25 | #### iOS 26 | 27 | 1. In XCode, in the project navigator, right click `Libraries` ➜ `Add Files to [your project's name]` 28 | 2. Go to `node_modules` ➜ `react-native-ssl-pinning` and add `RNSslPinning.xcodeproj` 29 | 3. In XCode, in the project navigator, select your project. Add `libRNSslPinning.a` to your project's `Build Phases` ➜ `Link Binary With Libraries` 30 | 4. Run your project (`Cmd+R`)< 31 | 32 | #### Android 33 | 34 | Add maven { url "https://jitpack.io" } to project level build.gradle like this: 35 | ``` 36 | allprojects { 37 | repositories { 38 | maven { url "https://jitpack.io" } 39 | } 40 | } 41 | ``` 42 | 1. Open up `android/app/src/main/java/[...]/MainActivity.java` 43 | - Add `import com.toyberman.RNSslPinningPackage;` to the imports at the top of the file 44 | - Add `new RNSslPinningPackage()` to the list returned by the `getPackages()` method 45 | 2. Append the following lines to `android/settings.gradle`: 46 | ``` 47 | include ':react-native-ssl-pinning' 48 | project(':react-native-ssl-pinning').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-ssl-pinning/android') 49 | ``` 50 | 3. Insert the following lines inside the dependencies block in `android/app/build.gradle`: 51 | ``` 52 | compile project(':react-native-ssl-pinning') 53 | ``` 54 | 55 | 56 | ## Usage 57 | 58 | #### Create the certificates: 59 | 60 | 1. openssl s_client -showcerts -servername google.com -connect google.com:443 { 121 | console.log(`response received ${response}`) 122 | }) 123 | .catch(err => { 124 | console.log(`error: ${err}`) 125 | }) 126 | ``` 127 | ### Public Key Pinning 128 | ```javascript 129 | import {fetch} from 'react-native-ssl-pinning'; 130 | 131 | fetch("https://publicobject.com", { 132 | method: "GET" , 133 | timeoutInterval: 10000, // milliseconds 134 | // your certificates array (needed only in android) ios will pick it automatically 135 | pkPinning: true, 136 | sslPinning: { 137 | certs: ["sha256//r8udi/Mxd6pLO7y7hZyUMWq8YnFnIWXCqeHsTDRqy8=", 138 | "sha256/YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=", 139 | "sha256/Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys=" 140 | ] 141 | }, 142 | headers: { 143 | Accept: "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*", "e_platform": "mobile", 144 | } 145 | }) 146 | 147 | ``` 148 | ### Disable Pinning 149 | ```javascript 150 | 151 | fetch("https://publicobject.com", { 152 | method: "GET" , 153 | timeoutInterval: 10000, // milliseconds 154 | disableAllSecurity: true, 155 | headers: { 156 | Accept: "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*", "e_platform": "mobile", 157 | } 158 | }) 159 | 160 | ``` 161 | ### Case Sensitive Headers 162 | ```javascript 163 | 164 | fetch("https://publicobject.com", { 165 | method: "GET" , 166 | timeoutInterval: 10000, // milliseconds 167 | caseSensitiveHeaders: true, //in case you want headers to be case Sensitive 168 | headers: { 169 | Accept: "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*", "e_platform": "mobile", 170 | SOAPAction: "testAction", 171 | } 172 | }) 173 | 174 | 175 | ``` 176 | ### Cookies Handling 177 | 178 | ```javascript 179 | import {removeCookieByName} from 'react-native-ssl-pinning'; 180 | 181 | 182 | removeCookieByName('cookieName') 183 | .then(res =>{ 184 | console.log('removeCookieByName'); 185 | }) 186 | 187 | getCookies('domain') 188 | .then(cookies => { 189 | // do what you need with your cookies 190 | }) 191 | 192 | ``` 193 | ## Multipart request (FormData) 194 | 195 | ```javascript 196 | let formData = new FormData() 197 | 198 | #You could add a key/value pair to this using #FormData.append: 199 | 200 | formData.append('username', 'Chris'); 201 | 202 | # Adding a file to the request 203 | formData.append('file', { 204 | name: encodeURIComponent(response.fileName), 205 | fileName: encodeURIComponent(response.fileName), 206 | type: this._extractFileType(response.fileName), 207 | uri: response.uri 208 | }) 209 | 210 | fetch(url, { 211 | method: "POST" , 212 | timeoutInterval: communication_timeout, // milliseconds 213 | body: { 214 | formData: request, 215 | }, 216 | sslPinning: { 217 | certs: ["cert1","cert2"] 218 | }, 219 | headers: { 220 | accept: 'application/json, text/plain, /', 221 | } 222 | }) 223 | 224 | don't add 'content-type': 'multipart/form-data; charset=UTF-8', 225 | Setting the Content-Type header manually means it's missing the boundary parameter. Remove that header and allow fetch to generate the full content type. 226 | ``` 227 | 228 | ## License 229 | This project is licensed under the terms of the MIT license. 230 | -------------------------------------------------------------------------------- /RNSslPinning.podspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, './package.json'))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = "RNSslPinning" 7 | s.version = package['version'] 8 | s.summary = package['description'] 9 | 10 | s.homepage = package['homepage'] 11 | s.license = package['license'] 12 | # s.license = { :type => "MIT", :file => "FILE_LICENSE" } 13 | s.author = { "author" => "author@domain.cn" } 14 | s.platform = :ios, "9.0" 15 | s.source = { :git => "https://github.com/MaxToyberman/react-native-ssl-pinning", :tag => "master" } 16 | s.source_files = "ios/RNSslPinning/**/*.{h,m}" 17 | s.requires_arc = true 18 | 19 | 20 | s.dependency "React" 21 | s.dependency "AFNetworking", "~> 4.0" 22 | 23 | end 24 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | buildscript { 3 | repositories { 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.3.0' 9 | } 10 | } 11 | 12 | apply plugin: 'com.android.library' 13 | 14 | android { 15 | compileSdkVersion 30 16 | buildToolsVersion "28.0.3" 17 | 18 | defaultConfig { 19 | minSdkVersion 16 20 | targetSdkVersion 28 21 | versionCode 1 22 | versionName "1.0" 23 | } 24 | lintOptions { 25 | abortOnError false 26 | } 27 | } 28 | 29 | repositories { 30 | mavenCentral() 31 | } 32 | 33 | dependencies { 34 | implementation "com.facebook.react:react-native:+" 35 | implementation "com.squareup.okhttp3:okhttp:4.9.0" 36 | implementation "com.squareup.okio:okio:2.6.0" 37 | implementation "com.github.franmontiel:PersistentCookieJar:v1.0.1" 38 | implementation "com.squareup.okhttp3:logging-interceptor:4.9.0" 39 | implementation "com.squareup.okhttp3:okhttp-urlconnection:4.9.0" 40 | } 41 | 42 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxToyberman/react-native-ssl-pinning/fd39ff17b5d1d9190682bb1919283435beac1772/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Feb 05 11:20:27 IST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/src/main/java/com/toyberman/RNSslPinningModule.java: -------------------------------------------------------------------------------- 1 | package com.toyberman; 2 | 3 | import android.os.Build; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import com.facebook.react.bridge.Arguments; 8 | import com.facebook.react.bridge.Callback; 9 | import com.facebook.react.bridge.Promise; 10 | import com.facebook.react.bridge.ReactApplicationContext; 11 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 12 | import com.facebook.react.bridge.ReactMethod; 13 | import com.facebook.react.bridge.ReadableArray; 14 | import com.facebook.react.bridge.ReadableMap; 15 | import com.facebook.react.bridge.WritableMap; 16 | import com.facebook.react.bridge.WritableNativeMap; 17 | import com.facebook.react.modules.network.ForwardingCookieHandler; 18 | import com.toyberman.Utils.OkHttpUtils; 19 | 20 | import org.json.JSONException; 21 | 22 | import java.io.IOException; 23 | import java.net.URI; 24 | import java.net.URISyntaxException; 25 | import java.util.ArrayList; 26 | import java.util.Base64; 27 | import java.util.Collections; 28 | import java.util.HashMap; 29 | import java.util.List; 30 | import java.util.Map; 31 | import java.util.Set; 32 | import java.util.concurrent.ConcurrentHashMap; 33 | import java.util.concurrent.CopyOnWriteArrayList; 34 | 35 | import okhttp3.Call; 36 | import okhttp3.Cookie; 37 | import okhttp3.CookieJar; 38 | import okhttp3.Headers; 39 | import okhttp3.HttpUrl; 40 | import okhttp3.OkHttpClient; 41 | import okhttp3.Request; 42 | import okhttp3.Response; 43 | 44 | public class RNSslPinningModule extends ReactContextBaseJavaModule { 45 | 46 | 47 | private static final String OPT_SSL_PINNING_KEY = "sslPinning"; 48 | private static final String DISABLE_ALL_SECURITY = "disableAllSecurity"; 49 | private static final String RESPONSE_TYPE = "responseType"; 50 | private static final String KEY_NOT_ADDED_ERROR = "sslPinning key was not added"; 51 | 52 | private final ReactApplicationContext reactContext; 53 | private final Map> cookieStore; 54 | private CookieJar cookieJar = null; 55 | private ForwardingCookieHandler cookieHandler; 56 | private OkHttpClient client; 57 | 58 | public RNSslPinningModule(ReactApplicationContext reactContext) { 59 | super(reactContext); 60 | this.reactContext = reactContext; 61 | cookieStore = new ConcurrentHashMap<>(); 62 | cookieHandler = new ForwardingCookieHandler(reactContext); 63 | cookieJar = new CookieJar() { 64 | 65 | @Override 66 | public synchronized void saveFromResponse(HttpUrl url, List unmodifiableCookieList) { 67 | for (Cookie cookie : unmodifiableCookieList) { 68 | setCookie(url, cookie); 69 | } 70 | } 71 | 72 | @Override 73 | public synchronized List loadForRequest(HttpUrl url) { 74 | List cookies = cookieStore.get(url.host()); 75 | return cookies != null ? cookies : new ArrayList(); 76 | } 77 | 78 | public void setCookie(HttpUrl url, Cookie cookie) { 79 | 80 | final String host = url.host(); 81 | 82 | List cookieListForUrl = cookieStore.get(host); 83 | if (cookieListForUrl == null) { 84 | cookieListForUrl = new CopyOnWriteArrayList<>(); 85 | cookieStore.put(host, cookieListForUrl); 86 | } 87 | try { 88 | putCookie(url, cookieListForUrl, cookie); 89 | } catch (Exception e) { 90 | e.printStackTrace(); 91 | } 92 | } 93 | 94 | private void putCookie(HttpUrl url, List storedCookieList, Cookie newCookie) throws URISyntaxException, IOException { 95 | 96 | Cookie oldCookie = null; 97 | Map> cookieMap = new HashMap<>(); 98 | 99 | for (Cookie storedCookie : storedCookieList) { 100 | 101 | // create key for comparison 102 | final String oldCookieKey = storedCookie.name() + storedCookie.path(); 103 | final String newCookieKey = newCookie.name() + newCookie.path(); 104 | 105 | if (oldCookieKey.equals(newCookieKey)) { 106 | oldCookie = storedCookie; 107 | break; 108 | } 109 | } 110 | if (oldCookie != null) { 111 | storedCookieList.remove(oldCookie); 112 | } 113 | storedCookieList.add(newCookie); 114 | 115 | cookieMap.put("Set-cookie", Collections.singletonList(newCookie.toString())); 116 | cookieHandler.put(url.uri(), cookieMap); 117 | } 118 | }; 119 | 120 | } 121 | 122 | public static String getDomainName(String url) throws URISyntaxException { 123 | URI uri = new URI(url); 124 | String domain = uri.getHost(); 125 | return domain.startsWith("www.") ? domain.substring(4) : domain; 126 | } 127 | 128 | 129 | @ReactMethod 130 | public void getCookies(String domain, final Promise promise) { 131 | try { 132 | WritableMap map = new WritableNativeMap(); 133 | 134 | List cookies = cookieStore.get(getDomainName(domain)); 135 | 136 | if (cookies != null) { 137 | for (Cookie cookie : cookies) { 138 | map.putString(cookie.name(), cookie.value()); 139 | } 140 | } 141 | 142 | promise.resolve(map); 143 | } catch (Exception e) { 144 | promise.reject(e); 145 | } 146 | } 147 | 148 | 149 | @ReactMethod 150 | public void removeCookieByName(String cookieName, final Promise promise) { 151 | List cookies = null; 152 | 153 | for (String domain : cookieStore.keySet()) { 154 | List newCookiesList = new ArrayList<>(); 155 | 156 | cookies = cookieStore.get(domain); 157 | if (cookies != null) { 158 | for (Cookie cookie : cookies) { 159 | if (!cookie.name().equals(cookieName)) { 160 | newCookiesList.add(cookie); 161 | } 162 | } 163 | cookieStore.put(domain, newCookiesList); 164 | } 165 | } 166 | 167 | promise.resolve(null); 168 | } 169 | 170 | @ReactMethod 171 | public void fetch(String hostname, final ReadableMap options, final Callback callback) { 172 | 173 | final WritableMap response = Arguments.createMap(); 174 | String domainName; 175 | try { 176 | domainName = getDomainName(hostname); 177 | } catch (URISyntaxException e) { 178 | domainName = hostname; 179 | } 180 | if (options.hasKey(DISABLE_ALL_SECURITY) && options.getBoolean(DISABLE_ALL_SECURITY)) { 181 | client = OkHttpUtils.buildDefaultOHttpClient(cookieJar, domainName, options); 182 | } 183 | // With ssl pinning 184 | else if (options.hasKey(OPT_SSL_PINNING_KEY)) { 185 | if (options.getMap(OPT_SSL_PINNING_KEY).hasKey("certs")) { 186 | ReadableArray certs = options.getMap(OPT_SSL_PINNING_KEY).getArray("certs"); 187 | if (certs != null && certs.size() == 0) { 188 | throw new RuntimeException("certs array is empty"); 189 | } 190 | client = OkHttpUtils.buildOkHttpClient(cookieJar, domainName, certs, options); 191 | } else { 192 | callback.invoke(new Throwable("key certs was not found"), null); 193 | } 194 | } else { 195 | //no ssl pinning 196 | callback.invoke(new Throwable(KEY_NOT_ADDED_ERROR), null); 197 | return; 198 | } 199 | 200 | try { 201 | Request request = OkHttpUtils.buildRequest(this.reactContext, options, hostname); 202 | 203 | client.newCall(request).enqueue(new okhttp3.Callback() { 204 | @Override 205 | public void onFailure(Call call, IOException e) { 206 | callback.invoke(e.getMessage()); 207 | } 208 | 209 | @Override 210 | public void onResponse(Call call, Response okHttpResponse) throws IOException { 211 | byte[] bytes = okHttpResponse.body().bytes(); 212 | String stringResponse = new String(bytes, "UTF-8"); 213 | String responseType = ""; 214 | 215 | //build response headers map 216 | WritableMap headers = buildResponseHeaders(okHttpResponse); 217 | //set response status code 218 | response.putInt("status", okHttpResponse.code()); 219 | if (options.hasKey(RESPONSE_TYPE)) { 220 | responseType = options.getString(RESPONSE_TYPE); 221 | } 222 | switch (responseType) { 223 | case "base64": 224 | String base64; 225 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { 226 | base64 = android.util.Base64.encodeToString(bytes, android.util.Base64.DEFAULT); 227 | } else { 228 | base64 = Base64.getEncoder().encodeToString(bytes); 229 | } 230 | response.putString("data", base64); 231 | break; 232 | default: 233 | response.putString("bodyString", stringResponse); 234 | break; 235 | } 236 | response.putMap("headers", headers); 237 | 238 | if (okHttpResponse.isSuccessful()) { 239 | callback.invoke(null, response); 240 | } else { 241 | callback.invoke(response, null); 242 | } 243 | } 244 | }); 245 | 246 | 247 | } catch (JSONException e) { 248 | callback.invoke(e, null); 249 | } 250 | 251 | } 252 | 253 | @NonNull 254 | private WritableMap buildResponseHeaders(Response okHttpResponse) { 255 | Headers responseHeaders = okHttpResponse.headers(); 256 | Set headerNames = responseHeaders.names(); 257 | WritableMap headers = Arguments.createMap(); 258 | for (String header : headerNames) { 259 | headers.putString(header, responseHeaders.get(header)); 260 | } 261 | return headers; 262 | } 263 | 264 | @Override 265 | public String getName() { 266 | return "RNSslPinning"; 267 | } 268 | 269 | } 270 | -------------------------------------------------------------------------------- /android/src/main/java/com/toyberman/RNSslPinningPackage.java: -------------------------------------------------------------------------------- 1 | 2 | package com.toyberman; 3 | 4 | import java.util.Arrays; 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | import com.facebook.react.ReactPackage; 9 | import com.facebook.react.bridge.NativeModule; 10 | import com.facebook.react.bridge.ReactApplicationContext; 11 | import com.facebook.react.uimanager.ViewManager; 12 | import com.facebook.react.bridge.JavaScriptModule; 13 | public class RNSslPinningPackage implements ReactPackage { 14 | @Override 15 | public List createNativeModules(ReactApplicationContext reactContext) { 16 | return Arrays.asList(new RNSslPinningModule(reactContext)); 17 | } 18 | 19 | // Deprecated from RN 0.47 20 | public List> createJSModules() { 21 | return Collections.emptyList(); 22 | } 23 | 24 | @Override 25 | public List createViewManagers(ReactApplicationContext reactContext) { 26 | return Collections.emptyList(); 27 | } 28 | } -------------------------------------------------------------------------------- /android/src/main/java/com/toyberman/Utils/OkHttpUtils.java: -------------------------------------------------------------------------------- 1 | package com.toyberman.Utils; 2 | 3 | import android.content.Context; 4 | import android.net.Uri; 5 | 6 | import com.facebook.react.bridge.ReadableArray; 7 | import com.facebook.react.bridge.ReadableMap; 8 | import com.facebook.react.bridge.ReadableType; 9 | import com.toyberman.BuildConfig; 10 | 11 | import org.json.JSONException; 12 | 13 | import java.io.BufferedInputStream; 14 | import java.io.BufferedOutputStream; 15 | import java.io.File; 16 | import java.io.FileOutputStream; 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | import java.io.OutputStream; 20 | import java.security.KeyStore; 21 | import java.security.cert.Certificate; 22 | import java.security.cert.CertificateFactory; 23 | import java.util.Arrays; 24 | import java.util.HashMap; 25 | import java.util.concurrent.TimeUnit; 26 | 27 | import javax.net.ssl.SSLContext; 28 | import javax.net.ssl.TrustManager; 29 | import javax.net.ssl.TrustManagerFactory; 30 | import javax.net.ssl.X509TrustManager; 31 | 32 | import okhttp3.CertificatePinner; 33 | import okhttp3.CookieJar; 34 | import okhttp3.MediaType; 35 | import okhttp3.MultipartBody; 36 | import okhttp3.OkHttpClient; 37 | import okhttp3.Request; 38 | import okhttp3.RequestBody; 39 | import okhttp3.logging.HttpLoggingInterceptor; 40 | 41 | /** 42 | * Created by Max Toyberman on 2/11/18. 43 | */ 44 | 45 | public class OkHttpUtils { 46 | 47 | private static final String HEADERS_KEY = "headers"; 48 | private static final String BODY_KEY = "body"; 49 | private static final String METHOD_KEY = "method"; 50 | private static final String FILE = "file"; 51 | private static final HashMap clientsByDomain = new HashMap<>(); 52 | private static OkHttpClient defaultClient = null; 53 | // private static OkHttpClient client = null; 54 | private static SSLContext sslContext; 55 | private static String content_type = "application/json; charset=utf-8"; 56 | public static MediaType mediaType = MediaType.parse(content_type); 57 | 58 | public static OkHttpClient buildOkHttpClient(CookieJar cookieJar, String domainName, ReadableArray certs, ReadableMap options) { 59 | 60 | OkHttpClient client = null; 61 | CertificatePinner certificatePinner = null; 62 | if (!clientsByDomain.containsKey(domainName)) { 63 | // add logging interceptor 64 | HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); 65 | logging.setLevel(HttpLoggingInterceptor.Level.BODY); 66 | 67 | OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); 68 | clientBuilder.cookieJar(cookieJar); 69 | 70 | if (options.hasKey("pkPinning") && options.getBoolean("pkPinning")) { 71 | // public key pinning 72 | certificatePinner = initPublicKeyPinning(certs, domainName); 73 | clientBuilder.certificatePinner(certificatePinner); 74 | } else { 75 | // ssl pinning 76 | X509TrustManager manager = initSSLPinning(certs); 77 | clientBuilder 78 | .sslSocketFactory(sslContext.getSocketFactory(), manager); 79 | } 80 | 81 | 82 | if (BuildConfig.DEBUG) { 83 | clientBuilder.addInterceptor(logging); 84 | } 85 | 86 | client = clientBuilder 87 | .build(); 88 | 89 | 90 | clientsByDomain.put(domainName, client); 91 | } else { 92 | client = clientsByDomain.get(domainName); 93 | } 94 | 95 | 96 | 97 | if (options.hasKey("timeoutInterval")) { 98 | int timeout = options.getInt("timeoutInterval"); 99 | // Copy to customize OkHttp for this request. 100 | client = client.newBuilder() 101 | .readTimeout(timeout, TimeUnit.MILLISECONDS) 102 | .writeTimeout(timeout, TimeUnit.MILLISECONDS) 103 | .connectTimeout(timeout, TimeUnit.MILLISECONDS) 104 | .build(); 105 | } 106 | 107 | 108 | return client; 109 | 110 | } 111 | 112 | public static OkHttpClient buildDefaultOHttpClient(CookieJar cookieJar, String domainName, ReadableMap options) { 113 | 114 | 115 | if (defaultClient == null) { 116 | 117 | HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); 118 | logging.setLevel(HttpLoggingInterceptor.Level.BODY); 119 | 120 | OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); 121 | clientBuilder.cookieJar(cookieJar); 122 | 123 | if (BuildConfig.DEBUG) { 124 | clientBuilder.addInterceptor(logging); 125 | } 126 | 127 | defaultClient = clientBuilder.build(); 128 | } 129 | 130 | if (options.hasKey("timeoutInterval")) { 131 | int timeout = options.getInt("timeoutInterval"); 132 | 133 | defaultClient = defaultClient.newBuilder() 134 | .readTimeout(timeout, TimeUnit.MILLISECONDS) 135 | .writeTimeout(timeout, TimeUnit.MILLISECONDS) 136 | .connectTimeout(timeout, TimeUnit.MILLISECONDS) 137 | .build(); 138 | } 139 | 140 | return defaultClient; 141 | 142 | } 143 | 144 | private static CertificatePinner initPublicKeyPinning(ReadableArray pins, String domain) { 145 | 146 | 147 | CertificatePinner.Builder certificatePinnerBuilder = new CertificatePinner.Builder(); 148 | //add all keys to the certficates pinner 149 | for (int i = 0; i < pins.size(); i++) { 150 | certificatePinnerBuilder.add(domain, pins.getString(i)); 151 | } 152 | 153 | CertificatePinner certificatePinner = certificatePinnerBuilder.build(); 154 | 155 | return certificatePinner; 156 | 157 | } 158 | 159 | private static X509TrustManager initSSLPinning(ReadableArray certs) { 160 | X509TrustManager trustManager = null; 161 | try { 162 | sslContext = SSLContext.getInstance("TLS"); 163 | CertificateFactory cf = CertificateFactory.getInstance("X.509"); 164 | String keyStoreType = KeyStore.getDefaultType(); 165 | KeyStore keyStore = KeyStore.getInstance(keyStoreType); 166 | keyStore.load(null, null); 167 | 168 | for (int i = 0; i < certs.size(); i++) { 169 | String filename = certs.getString(i); 170 | InputStream caInput = new BufferedInputStream(OkHttpUtils.class.getClassLoader().getResourceAsStream("assets/" + filename + ".cer")); 171 | Certificate ca; 172 | try { 173 | ca = cf.generateCertificate(caInput); 174 | } finally { 175 | caInput.close(); 176 | } 177 | 178 | keyStore.setCertificateEntry(filename, ca); 179 | } 180 | 181 | String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); 182 | TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); 183 | tmf.init(keyStore); 184 | 185 | TrustManager[] trustManagers = tmf.getTrustManagers(); 186 | if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { 187 | throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers)); 188 | } 189 | trustManager = (X509TrustManager) trustManagers[0]; 190 | 191 | sslContext.init(null, new TrustManager[]{trustManager}, null); 192 | 193 | 194 | } catch (Exception e) { 195 | e.printStackTrace(); 196 | } 197 | return trustManager; 198 | } 199 | 200 | private static boolean isFilePart(ReadableArray part) { 201 | if (part.getType(1) != ReadableType.Map) { 202 | return false; 203 | } 204 | ReadableMap value = part.getMap(1); 205 | return value.hasKey("type") && (value.hasKey("uri") || value.hasKey("path")); 206 | } 207 | 208 | private static void addFormDataPart(Context context, MultipartBody.Builder multipartBodyBuilder, ReadableMap fileData, String key) { 209 | Uri _uri = Uri.parse(""); 210 | if (fileData.hasKey("uri")) { 211 | _uri = Uri.parse(fileData.getString("uri")); 212 | } else if (fileData.hasKey("path")) { 213 | _uri = Uri.parse(fileData.getString("path")); 214 | } 215 | String type = fileData.getString("type"); 216 | String fileName = ""; 217 | if (fileData.hasKey("fileName")) { 218 | fileName = fileData.getString("fileName"); 219 | } else if (fileData.hasKey("name")) { 220 | fileName = fileData.getString("name"); 221 | } 222 | 223 | try { 224 | File file = getTempFile(context, _uri); 225 | multipartBodyBuilder.addFormDataPart(key, fileName, RequestBody.create(MediaType.parse(type), file)); 226 | } catch (IOException e) { 227 | e.printStackTrace(); 228 | } 229 | } 230 | 231 | private static RequestBody buildFormDataRequestBody(Context context, ReadableMap formData) { 232 | MultipartBody.Builder multipartBodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM); 233 | multipartBodyBuilder.setType((MediaType.parse("multipart/form-data"))); 234 | if (formData.hasKey("_parts")) { 235 | ReadableArray parts = formData.getArray("_parts"); 236 | for (int i = 0; i < parts.size(); i++) { 237 | ReadableArray part = parts.getArray(i); 238 | String key = ""; 239 | if (part.getType(0) == ReadableType.String) { 240 | key = part.getString(0); 241 | } else if (part.getType(0) == ReadableType.Number) { 242 | key = String.valueOf(part.getInt(0)); 243 | } 244 | 245 | if (isFilePart(part)) { 246 | ReadableMap fileData = part.getMap(1); 247 | addFormDataPart(context, multipartBodyBuilder, fileData, key); 248 | } else { 249 | String value = part.getString(1); 250 | multipartBodyBuilder.addFormDataPart(key, value); 251 | } 252 | } 253 | } 254 | return multipartBodyBuilder.build(); 255 | } 256 | 257 | public static Request buildRequest(Context context, ReadableMap options, String hostname) throws JSONException { 258 | 259 | Request.Builder requestBuilder = new Request.Builder(); 260 | RequestBody body = null; 261 | 262 | String method = "GET"; 263 | 264 | if (options.hasKey(HEADERS_KEY)) { 265 | setRequestHeaders(options, requestBuilder); 266 | } 267 | 268 | if (options.hasKey(METHOD_KEY)) { 269 | method = options.getString(METHOD_KEY); 270 | } 271 | 272 | if (options.hasKey(BODY_KEY)) { 273 | 274 | ReadableType bodyType = options.getType(BODY_KEY); 275 | switch (bodyType) { 276 | case String: 277 | body = RequestBody.create(mediaType, options.getString(BODY_KEY)); 278 | break; 279 | case Map: 280 | ReadableMap bodyMap = options.getMap(BODY_KEY); 281 | if (bodyMap.hasKey("formData")) { 282 | ReadableMap formData = bodyMap.getMap("formData"); 283 | body = buildFormDataRequestBody(context, formData); 284 | } else if (bodyMap.hasKey("_parts")) { 285 | body = buildFormDataRequestBody(context, bodyMap); 286 | } 287 | break; 288 | } 289 | 290 | } 291 | return requestBuilder 292 | .url(hostname) 293 | .method(method, body) 294 | .build(); 295 | } 296 | 297 | public static File getTempFile(Context context, Uri uri) throws IOException { 298 | File file = File.createTempFile("media", null); 299 | InputStream inputStream = context.getContentResolver().openInputStream(uri); 300 | OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file)); 301 | byte[] buffer = new byte[1024]; 302 | int len; 303 | while ((len = inputStream.read(buffer)) != -1) 304 | outputStream.write(buffer, 0, len); 305 | inputStream.close(); 306 | outputStream.close(); 307 | return file; 308 | } 309 | 310 | private static void setRequestHeaders(ReadableMap options, Request.Builder requestBuilder) { 311 | ReadableMap map = options.getMap((HEADERS_KEY)); 312 | //add headers to request 313 | Utilities.addHeadersFromMap(map, requestBuilder); 314 | if (map.hasKey("content-type")) { 315 | content_type = map.getString("content-type"); 316 | mediaType = MediaType.parse(content_type); 317 | } 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /android/src/main/java/com/toyberman/Utils/Utilities.java: -------------------------------------------------------------------------------- 1 | package com.toyberman.Utils; 2 | 3 | import com.facebook.react.bridge.ReadableMap; 4 | import com.facebook.react.bridge.ReadableMapKeySetIterator; 5 | 6 | 7 | import com.toyberman.BuildConfig; 8 | 9 | import java.io.File; 10 | import java.io.FileOutputStream; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.io.OutputStream; 14 | 15 | import okhttp3.Request; 16 | 17 | /** 18 | * Created by Max Toyberman on 2/5/18. 19 | */ 20 | 21 | public class Utilities { 22 | 23 | // Copy an InputStream to a File. 24 | public static void copyInputStreamToFile(InputStream in, File file) { 25 | OutputStream out = null; 26 | 27 | try { 28 | out = new FileOutputStream(file); 29 | byte[] buf = new byte[1024]; 30 | int len; 31 | while ((len = in.read(buf)) > 0) { 32 | out.write(buf, 0, len); 33 | } 34 | } catch (Exception e) { 35 | if (BuildConfig.DEBUG) { 36 | e.printStackTrace(); 37 | } 38 | } finally { 39 | // Ensure that the InputStreams are closed even if there's an exception. 40 | try { 41 | if (out != null) { 42 | out.close(); 43 | } 44 | 45 | // If you want to close the "in" InputStream yourself then remove this 46 | // from here but ensure that you close it yourself eventually. 47 | in.close(); 48 | } catch (IOException e) { 49 | if (BuildConfig.DEBUG) { 50 | e.printStackTrace(); 51 | } 52 | } 53 | } 54 | } 55 | 56 | 57 | /** 58 | * @param map - map of headers 59 | * @param builder - request builder, all headers will be added to this request 60 | */ 61 | public static void addHeadersFromMap(ReadableMap map, Request.Builder builder) { 62 | 63 | ReadableMapKeySetIterator iterator = map.keySetIterator(); 64 | while (iterator.hasNextKey()) { 65 | String key = iterator.nextKey(); 66 | builder.addHeader(key, map.getString(key)); 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export namespace ReactNativeSSLPinning { 2 | interface Cookies { 3 | [cookieName: string]: string; 4 | } 5 | 6 | interface Header { 7 | [headerName: string]: string; 8 | } 9 | 10 | interface Options { 11 | body?: string | object, 12 | responseType?: 'text' | 'base64', 13 | credentials?: string, 14 | headers?: Header; 15 | method?: 'DELETE' | 'GET' | 'POST' | 'PUT', 16 | pkPinning?: boolean, 17 | sslPinning: { 18 | certs: string[] 19 | }, 20 | timeoutInterval?: number, 21 | disableAllSecurity?: boolean, 22 | caseSensitiveHeaders?: boolean = false 23 | } 24 | 25 | interface Response { 26 | bodyString?: string; 27 | data?: string; 28 | headers: Header; 29 | status: number; 30 | url: string; 31 | json: () => Promise<{ [key: string]: any}>; 32 | text: () => Promise; 33 | } 34 | } 35 | 36 | export declare function fetch(url: string, options: ReactNativeSSLPinning.Options): Promise; 37 | export declare function getCookies(domain: string): Promise; 38 | export declare function removeCookieByName(cookieName: string): Promise; 39 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { NativeModules } from 'react-native'; 2 | import Q from 'q' 3 | 4 | const { RNSslPinning } = NativeModules; 5 | 6 | const fetch = (url, obj, callback) => { 7 | let deferred = Q.defer(); 8 | 9 | if (obj.headers) { 10 | obj.headers = Object.keys(obj.headers) 11 | .reduce((acc, key) => ({ 12 | ...acc, 13 | [obj.caseSensitiveHeaders ? key : key.toLowerCase()]: obj.headers[key] 14 | }), {}) 15 | } 16 | 17 | 18 | RNSslPinning.fetch(url, obj, (err, res) => { 19 | if (err && typeof err != 'object') { 20 | deferred.reject(err); 21 | } 22 | 23 | let data = err || res; 24 | 25 | if (typeof data === 'string') { 26 | data = { bodyString: data } 27 | } 28 | 29 | data.json = function() { 30 | return Q.fcall(function() { 31 | return JSON.parse(data.bodyString); 32 | }); 33 | }; 34 | 35 | data.text = function() { 36 | return Q.fcall(function() { 37 | return data.bodyString; 38 | }); 39 | }; 40 | 41 | data.url = url; 42 | 43 | if (err) { 44 | deferred.reject(data); 45 | } else { 46 | deferred.resolve(data); 47 | } 48 | 49 | deferred.promise.nodeify(callback); 50 | }); 51 | 52 | return deferred.promise; 53 | }; 54 | 55 | const getCookies = (domain) => { 56 | if(domain) { 57 | return RNSslPinning.getCookies(domain); 58 | } 59 | 60 | return Promise.reject("Domain cannot be empty") 61 | }; 62 | 63 | const removeCookieByName = (name) => { 64 | if(name) { 65 | return RNSslPinning.removeCookieByName(name); 66 | } 67 | 68 | return Promise.reject("Cookie Name cannot be empty") 69 | }; 70 | 71 | export { 72 | fetch, 73 | getCookies, 74 | removeCookieByName 75 | } 76 | -------------------------------------------------------------------------------- /index.js.flow: -------------------------------------------------------------------------------- 1 | // @flow 2 | export type Header = { 3 | [headerName: string]: string; 4 | } 5 | 6 | export type Cookies = { 7 | [cookieName: string]: string; 8 | } 9 | 10 | export type Options = { 11 | body?: string | {}, 12 | credentials?: string, 13 | headers?: Header, 14 | method?: 'DELETE' | 'GET' | 'POST' | 'PUT', 15 | sslPinning: { 16 | certs: Array 17 | }, 18 | timeoutInterval?: number, 19 | disableAllSecurity?: boolean 20 | } 21 | 22 | export type Response = { 23 | bodyString: string, 24 | headers: Header, 25 | status: number, 26 | url: string, 27 | json: () => Promise<{ [key: string]: any}>, 28 | text: () => Promise, 29 | } 30 | 31 | declare export function fetch(url: string, options: Options): Promise; 32 | declare export function removeCookieByName(cookieName: string): Promise 33 | declare export function getCookies(domain: string): Promise 34 | -------------------------------------------------------------------------------- /ios/RNSslPinning.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 28E2427422F079E20057D175 /* RNSslPinning.m in Sources */ = {isa = PBXBuildFile; fileRef = 28E2427222F079E20057D175 /* RNSslPinning.m */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXCopyFilesBuildPhase section */ 14 | 58B511D91A9E6C8500147676 /* CopyFiles */ = { 15 | isa = PBXCopyFilesBuildPhase; 16 | buildActionMask = 2147483647; 17 | dstPath = "include/$(PRODUCT_NAME)"; 18 | dstSubfolderSpec = 16; 19 | files = ( 20 | ); 21 | runOnlyForDeploymentPostprocessing = 0; 22 | }; 23 | /* End PBXCopyFilesBuildPhase section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | 134814201AA4EA6300B7C361 /* libRNSslPinning.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNSslPinning.a; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | 28E2427222F079E20057D175 /* RNSslPinning.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSslPinning.m; path = RNSslPinning/RNSslPinning.m; sourceTree = ""; }; 28 | 28E2427322F079E20057D175 /* RNSslPinning.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSslPinning.h; path = RNSslPinning/RNSslPinning.h; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 58B511D81A9E6C8500147676 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | 134814211AA4EA7D00B7C361 /* Products */ = { 43 | isa = PBXGroup; 44 | children = ( 45 | 134814201AA4EA6300B7C361 /* libRNSslPinning.a */, 46 | ); 47 | name = Products; 48 | sourceTree = ""; 49 | }; 50 | 58B511D21A9E6C8500147676 = { 51 | isa = PBXGroup; 52 | children = ( 53 | 28E2427322F079E20057D175 /* RNSslPinning.h */, 54 | 28E2427222F079E20057D175 /* RNSslPinning.m */, 55 | 134814211AA4EA7D00B7C361 /* Products */, 56 | ); 57 | sourceTree = ""; 58 | }; 59 | /* End PBXGroup section */ 60 | 61 | /* Begin PBXNativeTarget section */ 62 | 58B511DA1A9E6C8500147676 /* RNSslPinning */ = { 63 | isa = PBXNativeTarget; 64 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNSslPinning" */; 65 | buildPhases = ( 66 | 58B511D71A9E6C8500147676 /* Sources */, 67 | 58B511D81A9E6C8500147676 /* Frameworks */, 68 | 58B511D91A9E6C8500147676 /* CopyFiles */, 69 | ); 70 | buildRules = ( 71 | ); 72 | dependencies = ( 73 | ); 74 | name = RNSslPinning; 75 | productName = RCTDataManager; 76 | productReference = 134814201AA4EA6300B7C361 /* libRNSslPinning.a */; 77 | productType = "com.apple.product-type.library.static"; 78 | }; 79 | /* End PBXNativeTarget section */ 80 | 81 | /* Begin PBXProject section */ 82 | 58B511D31A9E6C8500147676 /* Project object */ = { 83 | isa = PBXProject; 84 | attributes = { 85 | LastUpgradeCheck = 0830; 86 | ORGANIZATIONNAME = Facebook; 87 | TargetAttributes = { 88 | 58B511DA1A9E6C8500147676 = { 89 | CreatedOnToolsVersion = 6.1.1; 90 | }; 91 | }; 92 | }; 93 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNSslPinning" */; 94 | compatibilityVersion = "Xcode 3.2"; 95 | developmentRegion = English; 96 | hasScannedForEncodings = 0; 97 | knownRegions = ( 98 | English, 99 | en, 100 | ); 101 | mainGroup = 58B511D21A9E6C8500147676; 102 | productRefGroup = 58B511D21A9E6C8500147676; 103 | projectDirPath = ""; 104 | projectRoot = ""; 105 | targets = ( 106 | 58B511DA1A9E6C8500147676 /* RNSslPinning */, 107 | ); 108 | }; 109 | /* End PBXProject section */ 110 | 111 | /* Begin PBXSourcesBuildPhase section */ 112 | 58B511D71A9E6C8500147676 /* Sources */ = { 113 | isa = PBXSourcesBuildPhase; 114 | buildActionMask = 2147483647; 115 | files = ( 116 | 28E2427422F079E20057D175 /* RNSslPinning.m in Sources */, 117 | ); 118 | runOnlyForDeploymentPostprocessing = 0; 119 | }; 120 | /* End PBXSourcesBuildPhase section */ 121 | 122 | /* Begin XCBuildConfiguration section */ 123 | 58B511ED1A9E6C8500147676 /* Debug */ = { 124 | isa = XCBuildConfiguration; 125 | buildSettings = { 126 | ALWAYS_SEARCH_USER_PATHS = NO; 127 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 128 | CLANG_CXX_LIBRARY = "libc++"; 129 | CLANG_ENABLE_MODULES = YES; 130 | CLANG_ENABLE_OBJC_ARC = YES; 131 | CLANG_WARN_BOOL_CONVERSION = YES; 132 | CLANG_WARN_CONSTANT_CONVERSION = YES; 133 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 134 | CLANG_WARN_EMPTY_BODY = YES; 135 | CLANG_WARN_ENUM_CONVERSION = YES; 136 | CLANG_WARN_INFINITE_RECURSION = YES; 137 | CLANG_WARN_INT_CONVERSION = YES; 138 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 139 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 140 | CLANG_WARN_UNREACHABLE_CODE = YES; 141 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 142 | COPY_PHASE_STRIP = NO; 143 | ENABLE_STRICT_OBJC_MSGSEND = YES; 144 | ENABLE_TESTABILITY = YES; 145 | GCC_C_LANGUAGE_STANDARD = gnu99; 146 | GCC_DYNAMIC_NO_PIC = NO; 147 | GCC_NO_COMMON_BLOCKS = YES; 148 | GCC_OPTIMIZATION_LEVEL = 0; 149 | GCC_PREPROCESSOR_DEFINITIONS = ( 150 | "DEBUG=1", 151 | "$(inherited)", 152 | ); 153 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 154 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 155 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 156 | GCC_WARN_UNDECLARED_SELECTOR = YES; 157 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 158 | GCC_WARN_UNUSED_FUNCTION = YES; 159 | GCC_WARN_UNUSED_VARIABLE = YES; 160 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 161 | MTL_ENABLE_DEBUG_INFO = YES; 162 | ONLY_ACTIVE_ARCH = YES; 163 | SDKROOT = iphoneos; 164 | }; 165 | name = Debug; 166 | }; 167 | 58B511EE1A9E6C8500147676 /* Release */ = { 168 | isa = XCBuildConfiguration; 169 | buildSettings = { 170 | ALWAYS_SEARCH_USER_PATHS = NO; 171 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 172 | CLANG_CXX_LIBRARY = "libc++"; 173 | CLANG_ENABLE_MODULES = YES; 174 | CLANG_ENABLE_OBJC_ARC = YES; 175 | CLANG_WARN_BOOL_CONVERSION = YES; 176 | CLANG_WARN_CONSTANT_CONVERSION = YES; 177 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 178 | CLANG_WARN_EMPTY_BODY = YES; 179 | CLANG_WARN_ENUM_CONVERSION = YES; 180 | CLANG_WARN_INFINITE_RECURSION = YES; 181 | CLANG_WARN_INT_CONVERSION = YES; 182 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 183 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 184 | CLANG_WARN_UNREACHABLE_CODE = YES; 185 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 186 | COPY_PHASE_STRIP = YES; 187 | ENABLE_NS_ASSERTIONS = NO; 188 | ENABLE_STRICT_OBJC_MSGSEND = YES; 189 | GCC_C_LANGUAGE_STANDARD = gnu99; 190 | GCC_NO_COMMON_BLOCKS = YES; 191 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 192 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 193 | GCC_WARN_UNDECLARED_SELECTOR = YES; 194 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 195 | GCC_WARN_UNUSED_FUNCTION = YES; 196 | GCC_WARN_UNUSED_VARIABLE = YES; 197 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 198 | MTL_ENABLE_DEBUG_INFO = NO; 199 | SDKROOT = iphoneos; 200 | VALIDATE_PRODUCT = YES; 201 | }; 202 | name = Release; 203 | }; 204 | 58B511F01A9E6C8500147676 /* Debug */ = { 205 | isa = XCBuildConfiguration; 206 | buildSettings = { 207 | HEADER_SEARCH_PATHS = ( 208 | "$(inherited)", 209 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 210 | "$(SRCROOT)/../../../React/**", 211 | "$(SRCROOT)/../../react-native/React/**", 212 | ); 213 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 214 | OTHER_LDFLAGS = "-ObjC"; 215 | PRODUCT_NAME = RNSslPinning; 216 | SKIP_INSTALL = YES; 217 | }; 218 | name = Debug; 219 | }; 220 | 58B511F11A9E6C8500147676 /* Release */ = { 221 | isa = XCBuildConfiguration; 222 | buildSettings = { 223 | HEADER_SEARCH_PATHS = ( 224 | "$(inherited)", 225 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 226 | "$(SRCROOT)/../../../React/**", 227 | "$(SRCROOT)/../../react-native/React/**", 228 | ); 229 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 230 | OTHER_LDFLAGS = "-ObjC"; 231 | PRODUCT_NAME = RNSslPinning; 232 | SKIP_INSTALL = YES; 233 | }; 234 | name = Release; 235 | }; 236 | /* End XCBuildConfiguration section */ 237 | 238 | /* Begin XCConfigurationList section */ 239 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNSslPinning" */ = { 240 | isa = XCConfigurationList; 241 | buildConfigurations = ( 242 | 58B511ED1A9E6C8500147676 /* Debug */, 243 | 58B511EE1A9E6C8500147676 /* Release */, 244 | ); 245 | defaultConfigurationIsVisible = 0; 246 | defaultConfigurationName = Release; 247 | }; 248 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNSslPinning" */ = { 249 | isa = XCConfigurationList; 250 | buildConfigurations = ( 251 | 58B511F01A9E6C8500147676 /* Debug */, 252 | 58B511F11A9E6C8500147676 /* Release */, 253 | ); 254 | defaultConfigurationIsVisible = 0; 255 | defaultConfigurationName = Release; 256 | }; 257 | /* End XCConfigurationList section */ 258 | }; 259 | rootObject = 58B511D31A9E6C8500147676 /* Project object */; 260 | } 261 | -------------------------------------------------------------------------------- /ios/RNSslPinning.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | 3 | 5 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/RNSslPinning/RNSslPinning.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #if __has_include("RCTBridgeModule.h") 4 | #import "RCTBridgeModule.h" 5 | #else 6 | #import 7 | #endif 8 | 9 | @interface RNSslPinning : NSObject 10 | 11 | @end 12 | 13 | -------------------------------------------------------------------------------- /ios/RNSslPinning/RNSslPinning.m: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Max Toyberman on 13/10/16. 3 | 4 | #import "RNSslPinning.h" 5 | #import "AFNetworking.h" 6 | 7 | @interface RNSslPinning() 8 | 9 | @property (nonatomic, strong) NSURLSessionConfiguration *sessionConfig; 10 | 11 | @end 12 | 13 | @implementation RNSslPinning 14 | RCT_EXPORT_MODULE(); 15 | 16 | - (instancetype)init 17 | { 18 | self = [super init]; 19 | if (self) { 20 | self.sessionConfig = [NSURLSessionConfiguration ephemeralSessionConfiguration]; 21 | self.sessionConfig.HTTPCookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; 22 | } 23 | return self; 24 | } 25 | 26 | RCT_EXPORT_METHOD(getCookies: (NSURL *)url resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){ 27 | 28 | NSHTTPCookie *cookie; 29 | NSHTTPCookieStorage* cookieJar = NSHTTPCookieStorage.sharedHTTPCookieStorage; 30 | 31 | NSMutableDictionary* dictionary = @{}.mutableCopy; 32 | 33 | for (cookie in [cookieJar cookiesForURL:url]) { 34 | [dictionary setObject:cookie.value forKey:cookie.name]; 35 | } 36 | 37 | if ([dictionary count] > 0){ 38 | resolve(dictionary); 39 | } 40 | else{ 41 | NSError *error = nil; 42 | reject(@"no_cookies", @"There were no cookies", error); 43 | } 44 | } 45 | 46 | 47 | 48 | RCT_EXPORT_METHOD(removeCookieByName: (NSString *)cookieName 49 | resolver:(RCTPromiseResolveBlock)resolve 50 | rejecter:(RCTPromiseRejectBlock)reject) { 51 | 52 | NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; 53 | for (NSHTTPCookie *cookie in cookieStorage.cookies) { 54 | // [cookieStorage deleteCookie:each]; 55 | NSString * name = cookie.name; 56 | 57 | if([cookieName isEqualToString:name]) { 58 | [cookieStorage deleteCookie:cookie]; 59 | } 60 | } 61 | 62 | resolve(nil); 63 | 64 | } 65 | 66 | 67 | -(void)performRequest:(AFURLSessionManager*)manager obj:(NSDictionary *)obj request:(NSMutableURLRequest*) request callback:(RCTResponseSenderBlock) callback { 68 | 69 | [[manager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { 70 | 71 | 72 | NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response; 73 | NSString *bodyString = [[NSString alloc] initWithData: responseObject encoding:NSUTF8StringEncoding]; 74 | NSInteger statusCode = httpResp.statusCode; 75 | 76 | if (!error) { 77 | // if(obj[@"responseType"]){ 78 | NSString * responseType = obj[@"responseType"]; 79 | 80 | if ([responseType isEqualToString:@"base64"]){ 81 | NSString* base64String = [responseObject base64EncodedStringWithOptions:0]; 82 | callback(@[[NSNull null], @{ 83 | @"status": @(statusCode), 84 | @"headers": httpResp.allHeaderFields, 85 | @"data": base64String 86 | }]); 87 | } 88 | else { 89 | callback(@[[NSNull null], @{ 90 | @"status": @(statusCode), 91 | @"headers": httpResp.allHeaderFields, 92 | @"bodyString": bodyString ? bodyString : @"" 93 | }]); 94 | } 95 | } else if (error && error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey]) { 96 | dispatch_async(dispatch_get_main_queue(), ^{ 97 | callback(@[@{ 98 | @"status": @(statusCode), 99 | @"headers": httpResp.allHeaderFields, 100 | @"bodyString": bodyString ? bodyString : @"" 101 | }, [NSNull null]]); 102 | }); 103 | } else { 104 | dispatch_async(dispatch_get_main_queue(), ^{ 105 | callback(@[error.localizedDescription, [NSNull null]]); 106 | }); 107 | } 108 | }] resume]; 109 | 110 | } 111 | 112 | 113 | -(void) setHeaders: (NSDictionary *)obj request:(NSMutableURLRequest*) request { 114 | 115 | if (obj[@"headers"] && [obj[@"headers"] isKindOfClass:[NSDictionary class]]) { 116 | NSMutableDictionary *m = [obj[@"headers"] mutableCopy]; 117 | for (NSString *key in [m allKeys]) { 118 | if (![m[key] isKindOfClass:[NSString class]]) { 119 | m[key] = [m[key] stringValue]; 120 | } 121 | } 122 | [request setAllHTTPHeaderFields:m]; 123 | } 124 | 125 | } 126 | 127 | - (BOOL) isFilePart: (NSArray*)part { 128 | if (![part[1] isKindOfClass:[NSDictionary class]]) { 129 | return NO; 130 | } 131 | NSDictionary * value = part[1]; 132 | return [value objectForKey:@"type"] && ([value objectForKey:@"name"] || [value objectForKey:@"fileName"]); 133 | } 134 | 135 | -(void) appendFormDataFilePart: (id) formData fileData: (NSArray*) fileData { 136 | NSString * key = fileData[0]; 137 | NSDictionary * value = fileData[1]; 138 | NSString * fileName = [value objectForKey:@"name"] ? [value objectForKey:@"name"] : [value objectForKey:@"fileName"]; 139 | NSString * mimeType = [value objectForKey:@"type"]; 140 | NSString * path = [value objectForKey:@"uri"] ? [value objectForKey:@"uri"] : [value objectForKey:@"path"]; 141 | 142 | [formData appendPartWithFileURL:[NSURL URLWithString:path] name:key fileName:fileName mimeType:mimeType error:nil]; 143 | } 144 | 145 | -(void) performMultipartRequest: (AFURLSessionManager*)manager obj:(NSDictionary *)obj url:(NSString *)url request:(NSMutableURLRequest*) request callback:(RCTResponseSenderBlock) callback formData:(NSDictionary*) formData { 146 | NSString * method = obj[@"method"] ? obj[@"method"] : @"POST"; 147 | 148 | NSMutableURLRequest *formDataRequest = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:method URLString:url parameters:nil constructingBodyWithBlock:^(id _formData) { 149 | if([formData objectForKey:@"_parts"]){ 150 | NSArray * parts = formData[@"_parts"]; 151 | for (int i = 0; i < [parts count]; i++) 152 | { 153 | NSArray * part = parts[i]; 154 | NSString * key = part[0]; 155 | 156 | if ([self isFilePart:part]) { 157 | [self appendFormDataFilePart:_formData fileData: part]; 158 | } else { 159 | NSString * value = part[1]; 160 | NSData *data = [value dataUsingEncoding:NSUTF8StringEncoding]; 161 | [_formData appendPartWithFormData:data name: key]; 162 | } 163 | } 164 | } 165 | } error:nil]; 166 | 167 | // Migrate header fields. 168 | [formDataRequest setAllHTTPHeaderFields:[request allHTTPHeaderFields]]; 169 | 170 | NSURLSessionUploadTask *uploadTask = [manager 171 | uploadTaskWithStreamedRequest:formDataRequest 172 | progress:^(NSProgress * _Nonnull uploadProgress) { 173 | NSLog(@"Upload progress %lld", uploadProgress.completedUnitCount / uploadProgress.totalUnitCount); 174 | } 175 | completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { 176 | NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response; 177 | NSString *bodyString = [[NSString alloc] initWithData: responseObject encoding:NSUTF8StringEncoding]; 178 | NSInteger statusCode = httpResp.statusCode; 179 | if (!error) { 180 | 181 | NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response; 182 | 183 | NSString *bodyString = [[NSString alloc] initWithData: responseObject encoding:NSUTF8StringEncoding]; 184 | NSInteger statusCode = httpResp.statusCode; 185 | 186 | NSDictionary *res = @{ 187 | @"status": @(statusCode), 188 | @"headers": httpResp.allHeaderFields, 189 | @"bodyString": bodyString ? bodyString : @"" 190 | }; 191 | callback(@[[NSNull null], res]); 192 | } 193 | else if (error && error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey]) { 194 | dispatch_async(dispatch_get_main_queue(), ^{ 195 | callback(@[@{ 196 | @"status": @(statusCode), 197 | @"headers": httpResp.allHeaderFields, 198 | @"bodyString": bodyString ? bodyString : @"" 199 | }, [NSNull null]]); 200 | }); 201 | } else { 202 | dispatch_async(dispatch_get_main_queue(), ^{ 203 | callback(@[error.localizedDescription, [NSNull null]]); 204 | }); 205 | } 206 | }]; 207 | 208 | [uploadTask resume]; 209 | } 210 | 211 | RCT_EXPORT_METHOD(fetch:(NSString *)url obj:(NSDictionary *)obj callback:(RCTResponseSenderBlock)callback) { 212 | NSURL *u = [NSURL URLWithString:url]; 213 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:u]; 214 | 215 | AFSecurityPolicy *policy; 216 | BOOL pkPinning = [[obj objectForKey:@"pkPinning"] boolValue]; 217 | BOOL disableAllSecurity = [[obj objectForKey:@"disableAllSecurity"] boolValue]; 218 | 219 | NSSet *certificates = [AFSecurityPolicy certificatesInBundle:[NSBundle mainBundle]]; 220 | 221 | // set policy (ssl pinning) 222 | if(disableAllSecurity){ 223 | policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone]; 224 | policy.validatesDomainName = false; 225 | policy.allowInvalidCertificates = true; 226 | } 227 | else if (pkPinning){ 228 | policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey withPinnedCertificates:certificates]; 229 | } 230 | else{ 231 | policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:certificates]; 232 | } 233 | 234 | AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; 235 | manager.securityPolicy = policy; 236 | 237 | manager.responseSerializer = [AFHTTPResponseSerializer serializer]; 238 | 239 | 240 | if (obj[@"method"]) { 241 | [request setHTTPMethod:obj[@"method"]]; 242 | } 243 | if (obj[@"timeoutInterval"]) { 244 | [request setTimeoutInterval:[obj[@"timeoutInterval"] doubleValue] / 1000]; 245 | } 246 | 247 | if(obj[@"headers"]) { 248 | [self setHeaders:obj request:request]; 249 | } 250 | 251 | if (obj) { 252 | 253 | if ([obj objectForKey:@"body"]) { 254 | NSDictionary * body = obj[@"body"]; 255 | 256 | // this is a multipart form data request 257 | if([body isKindOfClass:[NSDictionary class]]){ 258 | // post multipart 259 | if ([body objectForKey:@"formData"]) { 260 | [self performMultipartRequest:manager obj:obj url:url request:request callback:callback formData:body[@"formData"]]; 261 | } else if ([body objectForKey:@"_parts"]) { 262 | [self performMultipartRequest:manager obj:obj url:url request:request callback:callback formData:body]; 263 | } 264 | } 265 | else { 266 | 267 | // post a string 268 | NSData *data = [obj[@"body"] dataUsingEncoding:NSUTF8StringEncoding]; 269 | [request setHTTPBody:data]; 270 | [self performRequest:manager obj:obj request:request callback:callback ]; 271 | //TODO: if no body 272 | } 273 | 274 | } 275 | else { 276 | [self performRequest:manager obj:obj request:request callback:callback ]; 277 | } 278 | } 279 | else { 280 | 281 | } 282 | 283 | } 284 | 285 | + (BOOL)requiresMainQueueSetup 286 | { 287 | return YES; 288 | } 289 | 290 | @end 291 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-ssl-pinning", 3 | "version": "1.5.9", 4 | "description": "React-Native Ssl pinning using OkHttp 3 in Android, and AFNetworking on iOS.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "react-native" 11 | ], 12 | "types": "./index.d.ts", 13 | "author": "Max Toyberman", 14 | "dependencies": { 15 | "q": "^1.4.1" 16 | }, 17 | "license": "MIT", 18 | "homepage": "https://github.com/MaxToyberman/react-native-ssl-pinning#README", 19 | "peerDependencies": { 20 | "react-native": "*" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/MaxToyberman/react-native-ssl-pinning.git" 25 | } 26 | } 27 | --------------------------------------------------------------------------------