├── .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 javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
90 | Peer certificate chain:
91 | sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=: CN=publicobject.com, OU=PositiveSSL
92 | sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=: CN=COMODO RSA Secure Server CA
93 | sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=: CN=COMODO RSA Certification Authority
94 | sha256/lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=: CN=AddTrust External CA Root
95 | Pinned certificates for publicobject.com:
96 | sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
97 | at okhttp3.CertificatePinner.check(CertificatePinner.java)
98 | at okhttp3.Connection.upgradeToTls(Connection.java)
99 | at okhttp3.Connection.connect(Connection.java)
100 | at okhttp3.Connection.connectAndSetOwner(Connection.java)
101 | - Follow up by pasting the public key hashes from the exception into the certificate pinner's configuration
102 |
103 | ### Certificate Pinning
104 |
105 | ```javascript
106 | import {fetch} from 'react-native-ssl-pinning';
107 |
108 | fetch(url, {
109 | method: "POST" ,
110 | timeoutInterval: communication_timeout, // milliseconds
111 | body: body,
112 | // your certificates array (needed only in android) ios will pick it automatically
113 | sslPinning: {
114 | certs: ["cert1","cert2"] // your certificates name (without extension), for example cert1.cer, cert2.cer
115 | },
116 | headers: {
117 | Accept: "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*", "e_platform": "mobile",
118 | }
119 | })
120 | .then(response => {
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 |
--------------------------------------------------------------------------------