mFDList = new LinkedList<>();
44 | private LocalServerSocket mServerSocket;
45 | private boolean mWaitingForRelease = false;
46 | private long mLastHoldRelease = 0;
47 | private LocalSocket mServerSocketLocal;
48 | private pauseReason lastPauseReason = pauseReason.noNetwork;
49 | private PausedStateCallback mPauseCallback;
50 | private boolean mShuttingDown;
51 | private Runnable mResumeHoldRunnable = new Runnable() {
52 | @Override
53 | public void run() {
54 | if (shouldBeRunning()) {
55 | releaseHoldCmd();
56 | }
57 | }
58 | };
59 |
60 | public OpenVpnManagementThread(VpnProfile profile, OpenVPNService openVpnService) {
61 | mProfile = profile;
62 | mOpenVPNService = openVpnService;
63 | mResumeHandler = new Handler(openVpnService.getMainLooper());
64 | }
65 |
66 | private static boolean stopOpenVPN() {
67 | synchronized (active) {
68 | boolean sendCMD = false;
69 | for (OpenVpnManagementThread mt : active) {
70 | sendCMD = mt.managmentCommand("signal SIGINT\n");
71 | try {
72 | if (mt.mSocket != null) mt.mSocket.close();
73 | } catch (IOException e) {
74 | // Ignore close error on already closed socket
75 | }
76 | }
77 | return sendCMD;
78 | }
79 | }
80 |
81 | public boolean openManagementInterface(@NonNull Context c) {
82 | // Could take a while to open connection
83 | int tries = 8;
84 | String socketName = (c.getCacheDir().getAbsolutePath() + "/" + "mgmtsocket");
85 | // The mServerSocketLocal is transferred to the LocalServerSocket, ignore warning
86 | mServerSocketLocal = new LocalSocket();
87 | while (tries > 0 && !mServerSocketLocal.isBound()) {
88 | try {
89 | mServerSocketLocal.bind(new LocalSocketAddress(socketName, LocalSocketAddress.Namespace.FILESYSTEM));
90 | } catch (IOException e) {
91 | // wait 300 ms before retrying
92 | try {
93 | Thread.sleep(300);
94 | } catch (InterruptedException ignored) {
95 | }
96 | }
97 | tries--;
98 | }
99 | try {
100 | mServerSocket = new LocalServerSocket(mServerSocketLocal.getFileDescriptor());
101 | return true;
102 | } catch (IOException e) {
103 | VpnStatus.logException(e);
104 | }
105 | return false;
106 | }
107 |
108 | /**
109 | * @param cmd command to write to management socket
110 | * @return true if command have been sent
111 | */
112 | public boolean managmentCommand(String cmd) {
113 | try {
114 | if (mSocket != null && mSocket.getOutputStream() != null) {
115 | mSocket.getOutputStream().write(cmd.getBytes());
116 | mSocket.getOutputStream().flush();
117 | return true;
118 | }
119 | } catch (IOException e) {
120 | // Ignore socket stack traces
121 | }
122 | return false;
123 | }
124 |
125 | @Override
126 | public void run() {
127 | byte[] buffer = new byte[2048];
128 | // mSocket.setSoTimeout(5); // Setting a timeout cannot be that bad
129 | String pendingInput = "";
130 | synchronized (active) {
131 | active.add(this);
132 | }
133 | try {
134 | // Wait for a client to connect
135 | mSocket = mServerSocket.accept();
136 | InputStream instream = mSocket.getInputStream();
137 | // Close the management socket after client connected
138 | try {
139 | mServerSocket.close();
140 | } catch (IOException e) {
141 | VpnStatus.logException(e);
142 | }
143 | // Closing one of the two sockets also closes the other
144 | //mServerSocketLocal.close();
145 | while (true) {
146 | int numbytesread = instream.read(buffer);
147 | if (numbytesread == -1) return;
148 | FileDescriptor[] fds = null;
149 | try {
150 | fds = mSocket.getAncillaryFileDescriptors();
151 | } catch (IOException e) {
152 | VpnStatus.logException("Error reading fds from socket", e);
153 | }
154 | if (fds != null) {
155 | Collections.addAll(mFDList, fds);
156 | }
157 | String input = new String(buffer, 0, numbytesread, "UTF-8");
158 | pendingInput += input;
159 | pendingInput = processInput(pendingInput);
160 | }
161 | } catch (IOException e) {
162 | if (!e.getMessage().equals("socket closed") && !e.getMessage().equals("Connection reset by peer")) VpnStatus.logException(e);
163 | }
164 | synchronized (active) {
165 | active.remove(this);
166 | }
167 | }
168 |
169 | //! Hack O Rama 2000!
170 | private void protectFileDescriptor(FileDescriptor fd) {
171 | try {
172 | Method getInt = FileDescriptor.class.getDeclaredMethod("getInt$");
173 | int fdint = (Integer) getInt.invoke(fd);
174 | // You can even get more evil by parsing toString() and extract the int from that :)
175 | boolean result = mOpenVPNService.protect(fdint);
176 | if (!result) VpnStatus.logWarning("Could not protect VPN socket");
177 | //ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(fdint);
178 | //pfd.close();
179 | NativeUtils.jniclose(fdint);
180 | return;
181 | } catch (NoSuchMethodException | IllegalArgumentException | InvocationTargetException | IllegalAccessException | NullPointerException e) {
182 | VpnStatus.logException("Failed to retrieve fd from socket (" + fd + ")", e);
183 | }
184 | Log.d("Openvpn", "Failed to retrieve fd from socket: " + fd);
185 | }
186 |
187 | private String processInput(String pendingInput) {
188 | while (pendingInput.contains("\n")) {
189 | String[] tokens = pendingInput.split("\\r?\\n", 2);
190 | processCommand(tokens[0]);
191 | if (tokens.length == 1)
192 | // No second part, newline was at the end
193 | pendingInput = "";
194 | else pendingInput = tokens[1];
195 | }
196 | return pendingInput;
197 | }
198 |
199 | private void processCommand(String command) {
200 | //Log.i(TAG, "Line from managment" + command);
201 | if (command.startsWith(">") && command.contains(":")) {
202 | String[] parts = command.split(":", 2);
203 | String cmd = parts[0].substring(1);
204 | String argument = parts[1];
205 | switch (cmd) {
206 | case "INFO":
207 | /* Ignore greeting from management */
208 | return;
209 | case "PASSWORD":
210 | processPWCommand(argument);
211 | break;
212 | case "HOLD":
213 | handleHold(argument);
214 | break;
215 | case "NEED-OK":
216 | processNeedCommand(argument);
217 | break;
218 | case "BYTECOUNT":
219 | processByteCount(argument);
220 | break;
221 | case "STATE":
222 | if (!mShuttingDown) processState(argument);
223 | break;
224 | case "PROXY":
225 | processProxyCMD(argument);
226 | break;
227 | case "LOG":
228 | processLogMessage(argument);
229 | break;
230 | case "RSA_SIGN":
231 | processSignCommand(argument);
232 | break;
233 | default:
234 | VpnStatus.logWarning("MGMT: Got unrecognized command" + command);
235 | Log.i(TAG, "Got unrecognized command" + command);
236 | break;
237 | }
238 | } else if (command.startsWith("SUCCESS:")) {
239 | /* Ignore this kind of message too */
240 | return;
241 | } else if (command.startsWith("PROTECTFD: ")) {
242 | FileDescriptor fdtoprotect = mFDList.pollFirst();
243 | if (fdtoprotect != null) protectFileDescriptor(fdtoprotect);
244 | } else {
245 | Log.i(TAG, "Got unrecognized line from managment" + command);
246 | VpnStatus.logWarning("MGMT: Got unrecognized line from management:" + command);
247 | }
248 | }
249 |
250 | private void processLogMessage(String argument) {
251 | String[] args = argument.split(",", 4);
252 | // 0 unix time stamp
253 | // 1 log level N,I,E etc.
254 | /*
255 | (b) zero or more message flags in a single string:
256 | I -- informational
257 | F -- fatal error
258 | N -- non-fatal error
259 | W -- warning
260 | D -- debug, and
261 | */
262 | // 2 log message
263 | Log.d("OpenVPN", argument);
264 | VpnStatus.LogLevel level;
265 | switch (args[1]) {
266 | case "I":
267 | level = VpnStatus.LogLevel.INFO;
268 | break;
269 | case "W":
270 | level = VpnStatus.LogLevel.WARNING;
271 | break;
272 | case "D":
273 | level = VpnStatus.LogLevel.VERBOSE;
274 | break;
275 | case "F":
276 | level = VpnStatus.LogLevel.ERROR;
277 | break;
278 | default:
279 | level = VpnStatus.LogLevel.INFO;
280 | break;
281 | }
282 | int ovpnlevel = Integer.parseInt(args[2]) & 0x0F;
283 | String msg = args[3];
284 | if (msg.startsWith("MANAGEMENT: CMD")) ovpnlevel = Math.max(4, ovpnlevel);
285 | VpnStatus.logMessageOpenVPN(level, ovpnlevel, msg);
286 | }
287 |
288 | boolean shouldBeRunning() {
289 | if (mPauseCallback == null) return false;
290 | else return mPauseCallback.shouldBeRunning();
291 | }
292 |
293 | private void handleHold(String argument) {
294 | int waittime = Integer.parseInt(argument.split(":")[1]);
295 | if (shouldBeRunning()) {
296 | if (waittime > 1) VpnStatus.updateStateString("CONNECTRETRY", String.valueOf(waittime), R.string.state_waitconnectretry, ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET);
297 | mResumeHandler.postDelayed(mResumeHoldRunnable, waittime * 1000);
298 | if (waittime > 5) {
299 | VpnStatus.logInfo(R.string.state_waitconnectretry, String.valueOf(waittime));
300 | }
301 | } else {
302 | mWaitingForRelease = true;
303 | VpnStatus.updateStatePause(lastPauseReason);
304 | }
305 | }
306 |
307 | private void releaseHoldCmd() {
308 | mResumeHandler.removeCallbacks(mResumeHoldRunnable);
309 | if ((System.currentTimeMillis() - mLastHoldRelease) < 5000) {
310 | try {
311 | Thread.sleep(3000);
312 | } catch (InterruptedException ignored) {
313 | }
314 | }
315 | mWaitingForRelease = false;
316 | mLastHoldRelease = System.currentTimeMillis();
317 | managmentCommand("hold release\n");
318 | managmentCommand("bytecount " + mBytecountInterval + "\n");
319 | managmentCommand("state on\n");
320 | //managmentCommand("log on all\n");
321 | }
322 |
323 | public void releaseHold() {
324 | if (mWaitingForRelease) releaseHoldCmd();
325 | }
326 |
327 | private void processProxyCMD(String argument) {
328 | String[] args = argument.split(",", 3);
329 | SocketAddress proxyaddr = ProxyDetection.detectProxy(mProfile);
330 | if (args.length >= 2) {
331 | String proto = args[1];
332 | if (proto.equals("UDP")) {
333 | proxyaddr = null;
334 | }
335 | }
336 | if (proxyaddr instanceof InetSocketAddress) {
337 | InetSocketAddress isa = (InetSocketAddress) proxyaddr;
338 | VpnStatus.logInfo(R.string.using_proxy, isa.getHostName(), isa.getPort());
339 | String proxycmd = String.format(Locale.ENGLISH, "proxy HTTP %s %d\n", isa.getHostName(), isa.getPort());
340 | managmentCommand(proxycmd);
341 | } else {
342 | managmentCommand("proxy NONE\n");
343 | }
344 | }
345 |
346 | private void processState(String argument) {
347 | String[] args = argument.split(",", 3);
348 | String currentstate = args[1];
349 | if (args[2].equals(",,")) VpnStatus.updateStateString(currentstate, "");
350 | else VpnStatus.updateStateString(currentstate, args[2]);
351 | }
352 |
353 | private void processByteCount(String argument) {
354 | // >BYTECOUNT:{BYTES_IN},{BYTES_OUT}
355 | int comma = argument.indexOf(',');
356 | long in = Long.parseLong(argument.substring(0, comma));
357 | long out = Long.parseLong(argument.substring(comma + 1));
358 | VpnStatus.updateByteCount(in, out);
359 | }
360 |
361 | private void processNeedCommand(String argument) {
362 | int p1 = argument.indexOf('\'');
363 | int p2 = argument.indexOf('\'', p1 + 1);
364 | String needed = argument.substring(p1 + 1, p2);
365 | String extra = argument.split(":", 2)[1];
366 | String status = "ok";
367 | switch (needed) {
368 | case "PROTECTFD":
369 | FileDescriptor fdtoprotect = mFDList.pollFirst();
370 | protectFileDescriptor(fdtoprotect);
371 | break;
372 | case "DNSSERVER":
373 | mOpenVPNService.addDNS(extra);
374 | break;
375 | case "DNSDOMAIN":
376 | mOpenVPNService.setDomain(extra);
377 | break;
378 | case "ROUTE": {
379 | String[] routeparts = extra.split(" ");
380 | /*
381 | buf_printf (&out, "%s %s %s dev %s", network, netmask, gateway, rgi->iface);
382 | else
383 | buf_printf (&out, "%s %s %s", network, netmask, gateway);
384 | */
385 | if (routeparts.length == 5) {
386 | if (BuildConfig.DEBUG) Assert.assertEquals("dev", routeparts[3]);
387 | mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], routeparts[4]);
388 | } else if (routeparts.length >= 3) {
389 | mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], null);
390 | } else {
391 | VpnStatus.logError("Unrecognized ROUTE cmd:" + Arrays.toString(routeparts) + " | " + argument);
392 | }
393 | break;
394 | }
395 | case "ROUTE6": {
396 | String[] routeparts = extra.split(" ");
397 | mOpenVPNService.addRoutev6(routeparts[0], routeparts[1]);
398 | break;
399 | }
400 | case "IFCONFIG":
401 | String[] ifconfigparts = extra.split(" ");
402 | int mtu = Integer.parseInt(ifconfigparts[2]);
403 | mOpenVPNService.setLocalIP(ifconfigparts[0], ifconfigparts[1], mtu, ifconfigparts[3]);
404 | break;
405 | case "IFCONFIG6":
406 | mOpenVPNService.setLocalIPv6(extra);
407 | break;
408 | case "PERSIST_TUN_ACTION":
409 | // check if tun cfg stayed the same
410 | status = mOpenVPNService.getTunReopenStatus();
411 | break;
412 | case "OPENTUN":
413 | if (sendTunFD(needed, extra)) return;
414 | else status = "cancel";
415 | // This not nice or anything but setFileDescriptors accepts only FilDescriptor class :(
416 | break;
417 | default:
418 | Log.e(TAG, "Unknown needok command " + argument);
419 | return;
420 | }
421 | String cmd = String.format("needok '%s' %s\n", needed, status);
422 | managmentCommand(cmd);
423 | }
424 |
425 | private boolean sendTunFD(String needed, String extra) {
426 | if (!extra.equals("tun")) {
427 | // We only support tun
428 | VpnStatus.logError(String.format("Device type %s requested, but only tun is possible with the Android API, sorry!", extra));
429 | return false;
430 | }
431 | ParcelFileDescriptor pfd = mOpenVPNService.openTun();
432 | if (pfd == null) return false;
433 | Method setInt;
434 | int fdint = pfd.getFd();
435 | try {
436 | setInt = FileDescriptor.class.getDeclaredMethod("setInt$", int.class);
437 | FileDescriptor fdtosend = new FileDescriptor();
438 | setInt.invoke(fdtosend, fdint);
439 | FileDescriptor[] fds = {fdtosend};
440 | mSocket.setFileDescriptorsForSend(fds);
441 | // Trigger a send so we can close the fd on our side of the channel
442 | // The API documentation fails to mention that it will not reset the file descriptor to
443 | // be send and will happily send the file descriptor on every write ...
444 | String cmd = String.format("needok '%s' %s\n", needed, "ok");
445 | managmentCommand(cmd);
446 | // Set the FileDescriptor to null to stop this mad behavior
447 | mSocket.setFileDescriptorsForSend(null);
448 | pfd.close();
449 | return true;
450 | } catch (NoSuchMethodException | IllegalArgumentException | InvocationTargetException |
451 | IOException | IllegalAccessException exp) {
452 | VpnStatus.logException("Could not send fd over socket", exp);
453 | }
454 | return false;
455 | }
456 |
457 | private void processPWCommand(String argument) {
458 | //argument has the form Need 'Private Key' password
459 | // or ">PASSWORD:Verification Failed: '%s' ['%s']"
460 | String needed;
461 | try {
462 | int p1 = argument.indexOf('\'');
463 | int p2 = argument.indexOf('\'', p1 + 1);
464 | needed = argument.substring(p1 + 1, p2);
465 | if (argument.startsWith("Verification Failed")) {
466 | proccessPWFailed(needed, argument.substring(p2 + 1));
467 | return;
468 | }
469 | } catch (StringIndexOutOfBoundsException sioob) {
470 | VpnStatus.logError("Could not parse management Password command: " + argument);
471 | return;
472 | }
473 | String pw = null;
474 | if (needed.equals("Private Key")) {
475 | pw = mProfile.getPasswordPrivateKey();
476 | } else if (needed.equals("Auth")) {
477 | String usercmd = String.format("username '%s' %s\n", needed, VpnProfile.openVpnEscape(mProfile.mUsername));
478 | managmentCommand(usercmd);
479 | pw = mProfile.getPasswordAuth();
480 | }
481 | if (pw != null) {
482 | String cmd = String.format("password '%s' %s\n", needed, VpnProfile.openVpnEscape(pw));
483 | managmentCommand(cmd);
484 | } else {
485 | VpnStatus.logError(String.format("Openvpn requires Authentication type '%s' but no password/key information available", needed));
486 | }
487 | }
488 |
489 | private void proccessPWFailed(String needed, String args) {
490 | VpnStatus.updateStateString("AUTH_FAILED", needed + args, R.string.state_auth_failed, ConnectionStatus.LEVEL_AUTH_FAILED);
491 | }
492 |
493 | @Override
494 | public void networkChange(boolean samenetwork) {
495 | if (mWaitingForRelease) releaseHold();
496 | else if (samenetwork) managmentCommand("network-change samenetwork\n");
497 | else managmentCommand("network-change\n");
498 | }
499 |
500 | @Override
501 | public void setPauseCallback(PausedStateCallback callback) {
502 | mPauseCallback = callback;
503 | }
504 |
505 | public void signalusr1() {
506 | mResumeHandler.removeCallbacks(mResumeHoldRunnable);
507 | if (!mWaitingForRelease) managmentCommand("signal SIGUSR1\n");
508 | else
509 | // If signalusr1 is called update the state string
510 | // if there is another for stopping
511 | VpnStatus.updateStatePause(lastPauseReason);
512 | }
513 |
514 | public void reconnect() {
515 | signalusr1();
516 | releaseHold();
517 | }
518 |
519 | private void processSignCommand(String b64data) {
520 | String signed_string = mProfile.getSignedData(b64data);
521 | if (signed_string == null) {
522 | managmentCommand("rsa-sig\n");
523 | managmentCommand("\nEND\n");
524 | stopOpenVPN();
525 | return;
526 | }
527 | managmentCommand("rsa-sig\n");
528 | managmentCommand(signed_string);
529 | managmentCommand("\nEND\n");
530 | }
531 |
532 | @Override
533 | public void pause(pauseReason reason) {
534 | lastPauseReason = reason;
535 | signalusr1();
536 | }
537 |
538 | @Override
539 | public void resume() {
540 | releaseHold();
541 | /* Reset the reason why we are disconnected */
542 | lastPauseReason = pauseReason.noNetwork;
543 | }
544 |
545 | @Override
546 | public boolean stopVPN(boolean replaceConnection) {
547 | boolean stopSucceed = stopOpenVPN();
548 | if (stopSucceed) {
549 | mShuttingDown = true;
550 | }
551 | return stopSucceed;
552 | }
553 | }
554 |
--------------------------------------------------------------------------------
/vpn/src/main/java/de/blinkt/openvpn/core/PRNGFixes.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2016 Arne Schwabe
3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
4 | */
5 | package de.blinkt.openvpn.core;/*
6 | * This software is provided 'as-is', without any express or implied
7 | * warranty. In no event will Google be held liable for any damages
8 | * arising from the use of this software.
9 | *
10 | * Permission is granted to anyone to use this software for any purpose,
11 | * including commercial applications, and to alter it and redistribute it
12 | * freely, as long as the origin is not misrepresented.
13 | */
14 |
15 | import android.os.Build;
16 | import android.os.Process;
17 | import android.util.Log;
18 |
19 | import java.io.ByteArrayOutputStream;
20 | import java.io.DataInputStream;
21 | import java.io.DataOutputStream;
22 | import java.io.File;
23 | import java.io.FileInputStream;
24 | import java.io.FileOutputStream;
25 | import java.io.IOException;
26 | import java.io.OutputStream;
27 | import java.io.UnsupportedEncodingException;
28 | import java.security.NoSuchAlgorithmException;
29 | import java.security.Provider;
30 | import java.security.SecureRandom;
31 | import java.security.SecureRandomSpi;
32 | import java.security.Security;
33 |
34 | /**
35 | * Fixes for the output of the default PRNG having low entropy.
36 | *
37 | * The fixes need to be applied via {@link #apply()} before any use of Java
38 | * Cryptography Architecture primitives. A good place to invoke them is in the
39 | * application's {@code onCreate}.
40 | */
41 | public final class PRNGFixes {
42 | private static final int VERSION_CODE_JELLY_BEAN = 16;
43 | private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
44 | private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = getBuildFingerprintAndDeviceSerial();
45 |
46 | /**
47 | * Hidden constructor to prevent instantiation.
48 | */
49 | private PRNGFixes() {
50 | }
51 |
52 | /**
53 | * Applies all fixes.
54 | *
55 | * @throws SecurityException if a fix is needed but could not be applied.
56 | */
57 | public static void apply() {
58 | applyOpenSSLFix();
59 | installLinuxPRNGSecureRandom();
60 | }
61 |
62 | /**
63 | * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
64 | * fix is not needed.
65 | *
66 | * @throws SecurityException if the fix is needed but could not be applied.
67 | */
68 | private static void applyOpenSSLFix() throws SecurityException {
69 | if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN) || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
70 | // No need to apply the fix
71 | return;
72 | }
73 | try {
74 | // Mix in the device- and invocation-specific seed.
75 | Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto").getMethod("RAND_seed", byte[].class).invoke(null, generateSeed());
76 | // Mix output of Linux PRNG into OpenSSL's PRNG
77 | int bytesRead = (Integer) Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto").getMethod("RAND_load_file", String.class, long.class).invoke(null, "/dev/urandom", 1024);
78 | if (bytesRead != 1024) {
79 | throw new IOException("Unexpected number of bytes read from Linux PRNG: " + bytesRead);
80 | }
81 | } catch (Exception e) {
82 | throw new SecurityException("Failed to seed OpenSSL PRNG", e);
83 | }
84 | }
85 |
86 | /**
87 | * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
88 | * default. Does nothing if the implementation is already the default or if
89 | * there is not need to install the implementation.
90 | *
91 | * @throws SecurityException if the fix is needed but could not be applied.
92 | */
93 | private static void installLinuxPRNGSecureRandom() throws SecurityException {
94 | if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
95 | // No need to apply the fix
96 | return;
97 | }
98 | // Install a Linux PRNG-based SecureRandom implementation as the
99 | // default, if not yet installed.
100 | Provider[] secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG");
101 | if ((secureRandomProviders == null) || (secureRandomProviders.length < 1) || (!LinuxPRNGSecureRandomProvider.class.equals(secureRandomProviders[0].getClass()))) {
102 | Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
103 | }
104 | // Assert that new SecureRandom() and
105 | // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
106 | // by the Linux PRNG-based SecureRandom implementation.
107 | SecureRandom rng1 = new SecureRandom();
108 | if (!LinuxPRNGSecureRandomProvider.class.equals(rng1.getProvider().getClass())) {
109 | throw new SecurityException("new SecureRandom() backed by wrong Provider: " + rng1.getProvider().getClass());
110 | }
111 | SecureRandom rng2;
112 | try {
113 | rng2 = SecureRandom.getInstance("SHA1PRNG");
114 | } catch (NoSuchAlgorithmException e) {
115 | throw new SecurityException("SHA1PRNG not available", e);
116 | }
117 | if (!LinuxPRNGSecureRandomProvider.class.equals(rng2.getProvider().getClass())) {
118 | throw new SecurityException("SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + " Provider: " + rng2.getProvider().getClass());
119 | }
120 | }
121 |
122 | /**
123 | * Generates a device- and invocation-specific seed to be mixed into the
124 | * Linux PRNG.
125 | */
126 | private static byte[] generateSeed() {
127 | try {
128 | ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
129 | DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer);
130 | seedBufferOut.writeLong(System.currentTimeMillis());
131 | seedBufferOut.writeLong(System.nanoTime());
132 | seedBufferOut.writeInt(Process.myPid());
133 | seedBufferOut.writeInt(Process.myUid());
134 | seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
135 | seedBufferOut.close();
136 | return seedBuffer.toByteArray();
137 | } catch (IOException e) {
138 | throw new SecurityException("Failed to generate seed", e);
139 | }
140 | }
141 |
142 | /**
143 | * Gets the hardware serial number of this device.
144 | *
145 | * @return serial number or {@code null} if not available.
146 | */
147 | private static String getDeviceSerialNumber() {
148 | // We're using the Reflection API because Build.SERIAL is only available
149 | // since API Level 9 (Gingerbread, Android 2.3).
150 | try {
151 | return (String) Build.class.getField("SERIAL").get(null);
152 | } catch (Exception ignored) {
153 | return null;
154 | }
155 | }
156 |
157 | private static byte[] getBuildFingerprintAndDeviceSerial() {
158 | StringBuilder result = new StringBuilder();
159 | String fingerprint = Build.FINGERPRINT;
160 | if (fingerprint != null) {
161 | result.append(fingerprint);
162 | }
163 | String serial = getDeviceSerialNumber();
164 | if (serial != null) {
165 | result.append(serial);
166 | }
167 | try {
168 | return result.toString().getBytes("UTF-8");
169 | } catch (UnsupportedEncodingException e) {
170 | throw new RuntimeException("UTF-8 encoding not supported");
171 | }
172 | }
173 |
174 | /**
175 | * {@code Provider} of {@code SecureRandom} engines which pass through
176 | * all requests to the Linux PRNG.
177 | */
178 | private static class LinuxPRNGSecureRandomProvider extends Provider {
179 | public LinuxPRNGSecureRandomProvider() {
180 | super("LinuxPRNG", 1.0, "A Linux-specific random number provider that uses" + " /dev/urandom");
181 | // Although /dev/urandom is not a SHA-1 PRNG, some apps
182 | // explicitly request a SHA1PRNG SecureRandom and we thus need to
183 | // prevent them from getting the default implementation whose output
184 | // may have low entropy.
185 | put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
186 | put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
187 | }
188 | }
189 |
190 | /**
191 | * {@link SecureRandomSpi} which passes all requests to the Linux PRNG
192 | * ({@code /dev/urandom}).
193 | */
194 | public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
195 | /*
196 | * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
197 | * are passed through to the Linux PRNG (/dev/urandom). Instances of
198 | * this class seed themselves by mixing in the current time, PID, UID,
199 | * build fingerprint, and hardware serial number (where available) into
200 | * Linux PRNG.
201 | *
202 | * Concurrency: Read requests to the underlying Linux PRNG are
203 | * serialized (on sLock) to ensure that multiple threads do not get
204 | * duplicated PRNG output.
205 | */
206 | private static final File URANDOM_FILE = new File("/dev/urandom");
207 | private static final Object sLock = new Object();
208 | /**
209 | * Input stream for reading from Linux PRNG or {@code null} if not yet
210 | * opened.
211 | *
212 | * @GuardedBy("sLock")
213 | */
214 | private static DataInputStream sUrandomIn;
215 | /**
216 | * Output stream for writing to Linux PRNG or {@code null} if not yet
217 | * opened.
218 | *
219 | * @GuardedBy("sLock")
220 | */
221 | private static OutputStream sUrandomOut;
222 | /**
223 | * Whether this engine instance has been seeded. This is needed because
224 | * each instance needs to seed itself if the client does not explicitly
225 | * seed it.
226 | */
227 | private boolean mSeeded;
228 |
229 | @Override
230 | protected void engineSetSeed(byte[] bytes) {
231 | try {
232 | OutputStream out;
233 | synchronized (sLock) {
234 | out = getUrandomOutputStream();
235 | }
236 | out.write(bytes);
237 | out.flush();
238 | } catch (IOException e) {
239 | // On a small fraction of devices /dev/urandom is not writable.
240 | // Log and ignore.
241 | Log.w(PRNGFixes.class.getSimpleName(), "Failed to mix seed into " + URANDOM_FILE);
242 | } finally {
243 | mSeeded = true;
244 | }
245 | }
246 |
247 | @Override
248 | protected void engineNextBytes(byte[] bytes) {
249 | if (!mSeeded) {
250 | // Mix in the device- and invocation-specific seed.
251 | engineSetSeed(generateSeed());
252 | }
253 | try {
254 | DataInputStream in;
255 | synchronized (sLock) {
256 | in = getUrandomInputStream();
257 | }
258 | synchronized (in) {
259 | in.readFully(bytes);
260 | }
261 | } catch (IOException e) {
262 | throw new SecurityException("Failed to read from " + URANDOM_FILE, e);
263 | }
264 | }
265 |
266 | @Override
267 | protected byte[] engineGenerateSeed(int size) {
268 | byte[] seed = new byte[size];
269 | engineNextBytes(seed);
270 | return seed;
271 | }
272 |
273 | private DataInputStream getUrandomInputStream() {
274 | synchronized (sLock) {
275 | if (sUrandomIn == null) {
276 | // NOTE: Consider inserting a BufferedInputStream between
277 | // DataInputStream and FileInputStream if you need higher
278 | // PRNG output performance and can live with future PRNG
279 | // output being pulled into this process prematurely.
280 | try {
281 | sUrandomIn = new DataInputStream(new FileInputStream(URANDOM_FILE));
282 | } catch (IOException e) {
283 | throw new SecurityException("Failed to open " + URANDOM_FILE + " for reading", e);
284 | }
285 | }
286 | return sUrandomIn;
287 | }
288 | }
289 |
290 | private OutputStream getUrandomOutputStream() throws IOException {
291 | synchronized (sLock) {
292 | if (sUrandomOut == null) {
293 | sUrandomOut = new FileOutputStream(URANDOM_FILE);
294 | }
295 | return sUrandomOut;
296 | }
297 | }
298 | }
299 | }
--------------------------------------------------------------------------------
/vpn/src/main/java/de/blinkt/openvpn/core/ProfileManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2016 Arne Schwabe
3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
4 | */
5 | package de.blinkt.openvpn.core;
6 |
7 | import android.app.Activity;
8 | import android.content.Context;
9 | import android.content.SharedPreferences;
10 | import android.content.SharedPreferences.Editor;
11 | import android.preference.PreferenceManager;
12 |
13 | import java.io.IOException;
14 | import java.io.ObjectInputStream;
15 | import java.io.ObjectOutputStream;
16 | import java.util.Collection;
17 | import java.util.HashMap;
18 | import java.util.HashSet;
19 | import java.util.Set;
20 |
21 | import de.blinkt.openvpn.VpnProfile;
22 |
23 | public class ProfileManager {
24 | private static final String PREFS_NAME = "VPNList";
25 | private static final String LAST_CONNECTED_PROFILE = "lastConnectedProfile";
26 | private static ProfileManager instance;
27 | private static VpnProfile mLastConnectedVpn = null;
28 | private static VpnProfile tmpprofile = null;
29 | private HashMap profiles = new HashMap<>();
30 |
31 | private ProfileManager() {
32 | }
33 |
34 | private static VpnProfile get(String key) {
35 | if (tmpprofile != null && tmpprofile.getUUIDString().equals(key)) {
36 | return tmpprofile;
37 | }
38 | if (instance == null) {
39 | return null;
40 | }
41 | return instance.profiles.get(key);
42 | }
43 |
44 | private static void checkInstance(Context context) {
45 | if (instance == null) {
46 | instance = new ProfileManager();
47 | instance.loadVPNList(context);
48 | }
49 | }
50 |
51 | synchronized public static ProfileManager getInstance(Context context) {
52 | checkInstance(context);
53 | return instance;
54 | }
55 |
56 | public static void setConntectedVpnProfileDisconnected(Context c) {
57 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c);
58 | Editor prefsedit = prefs.edit();
59 | prefsedit.putString(LAST_CONNECTED_PROFILE, null);
60 | prefsedit.apply();
61 | }
62 |
63 | /**
64 | * Sets the profile that is connected (to connect if the service restarts)
65 | */
66 | static void setConnectedVpnProfile(Context c, VpnProfile connectedProfile) {
67 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c);
68 | Editor prefsedit = prefs.edit();
69 | prefsedit.putString(LAST_CONNECTED_PROFILE, connectedProfile.getUUIDString());
70 | prefsedit.apply();
71 | mLastConnectedVpn = connectedProfile;
72 | }
73 |
74 | /**
75 | * Returns the profile that was last connected (to connect if the service restarts)
76 | */
77 | static VpnProfile getLastConnectedProfile(Context c) {
78 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c);
79 | String lastConnectedProfile = prefs.getString(LAST_CONNECTED_PROFILE, null);
80 | if (lastConnectedProfile != null) {
81 | return get(c, lastConnectedProfile);
82 | } else {
83 | return null;
84 | }
85 | }
86 |
87 | public static void setTemporaryProfile(VpnProfile tmp) {
88 | ProfileManager.tmpprofile = tmp;
89 | }
90 |
91 | public static boolean isTempProfile() {
92 | return mLastConnectedVpn == tmpprofile;
93 | }
94 |
95 | public static VpnProfile get(Context context, String profileUUID) {
96 | checkInstance(context);
97 | return get(profileUUID);
98 | }
99 |
100 | static VpnProfile getLastConnectedVpn() {
101 | return mLastConnectedVpn;
102 | }
103 |
104 | static VpnProfile getAlwaysOnVPN(Context context) {
105 | checkInstance(context);
106 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
107 | String uuid = prefs.getString("alwaysOnVpn", null);
108 | return get(uuid);
109 | }
110 |
111 | public Collection getProfiles() {
112 | return profiles.values();
113 | }
114 |
115 | public VpnProfile getProfileByName(String name) {
116 | for (VpnProfile vpnp : profiles.values()) {
117 | if (vpnp.getName().equals(name)) {
118 | return vpnp;
119 | }
120 | }
121 | return null;
122 | }
123 |
124 | private void saveProfileList(Context context) {
125 | SharedPreferences sharedprefs = context.getSharedPreferences(PREFS_NAME, Activity.MODE_PRIVATE);
126 | Editor editor = sharedprefs.edit();
127 | editor.putStringSet("vpnlist", profiles.keySet());
128 | // For reasing I do not understand at all
129 | // Android saves my prefs file only one time
130 | // if I remove the debug code below :(
131 | int counter = sharedprefs.getInt("counter", 0);
132 | editor.putInt("counter", counter + 1);
133 | editor.apply();
134 | }
135 |
136 | public void addProfile(VpnProfile profile) {
137 | profiles.put(profile.getUUID().toString(), profile);
138 | }
139 |
140 | public void saveProfile(Context context, VpnProfile profile) {
141 | ObjectOutputStream vpnfile;
142 | try {
143 | vpnfile = new ObjectOutputStream(context.openFileOutput((profile.getUUID().toString() + ".vp"), Activity.MODE_PRIVATE));
144 | vpnfile.writeObject(profile);
145 | vpnfile.flush();
146 | vpnfile.close();
147 | } catch (IOException e) {
148 | VpnStatus.logException("saving VPN profile", e);
149 | throw new RuntimeException(e);
150 | }
151 | }
152 |
153 | private void loadVPNList(Context context) {
154 | profiles = new HashMap<>();
155 | SharedPreferences listpref = context.getSharedPreferences(PREFS_NAME, Activity.MODE_PRIVATE);
156 | Set vlist = listpref.getStringSet("vpnlist", null);
157 | if (vlist == null) {
158 | vlist = new HashSet<>();
159 | }
160 | for (String vpnentry : vlist) {
161 | try {
162 | ObjectInputStream vpnfile = new ObjectInputStream(context.openFileInput(vpnentry + ".vp"));
163 | VpnProfile vp = ((VpnProfile) vpnfile.readObject());
164 | // Sanity check
165 | if (vp == null || vp.mName == null || vp.getUUID() == null) continue;
166 | vp.upgradeProfile();
167 | profiles.put(vp.getUUID().toString(), vp);
168 | } catch (IOException | ClassNotFoundException e) {
169 | VpnStatus.logException("Loading VPN List", e);
170 | }
171 | }
172 | }
173 |
174 | public void removeProfile(Context context, VpnProfile profile) {
175 | String vpnentry = profile.getUUID().toString();
176 | profiles.remove(vpnentry);
177 | saveProfileList(context);
178 | context.deleteFile(vpnentry + ".vp");
179 | if (mLastConnectedVpn == profile) mLastConnectedVpn = null;
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/vpn/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2016 Arne Schwabe
3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
4 | */
5 | package de.blinkt.openvpn.core;
6 |
7 | import java.net.InetSocketAddress;
8 | import java.net.MalformedURLException;
9 | import java.net.Proxy;
10 | import java.net.ProxySelector;
11 | import java.net.SocketAddress;
12 | import java.net.URISyntaxException;
13 | import java.net.URL;
14 | import java.util.List;
15 |
16 | import de.blinkt.openvpn.R;
17 | import de.blinkt.openvpn.VpnProfile;
18 |
19 | public class ProxyDetection {
20 | static SocketAddress detectProxy(VpnProfile vp) {
21 | // Construct a new url with https as protocol
22 | try {
23 | URL url = new URL(String.format("https://%s:%s", vp.mServerName, vp.mServerPort));
24 | Proxy proxy = getFirstProxy(url);
25 | if (proxy == null) return null;
26 | SocketAddress addr = proxy.address();
27 | if (addr instanceof InetSocketAddress) {
28 | return addr;
29 | }
30 | } catch (MalformedURLException e) {
31 | VpnStatus.logError(R.string.getproxy_error, e.getLocalizedMessage());
32 | } catch (URISyntaxException e) {
33 | VpnStatus.logError(R.string.getproxy_error, e.getLocalizedMessage());
34 | }
35 | return null;
36 | }
37 |
38 | static Proxy getFirstProxy(URL url) throws URISyntaxException {
39 | System.setProperty("java.net.useSystemProxies", "true");
40 | List proxylist = ProxySelector.getDefault().select(url.toURI());
41 | if (proxylist != null) {
42 | for (Proxy proxy : proxylist) {
43 | SocketAddress addr = proxy.address();
44 | if (addr != null) {
45 | return proxy;
46 | }
47 | }
48 | }
49 | return null;
50 | }
51 | }
--------------------------------------------------------------------------------
/vpn/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2016 Arne Schwabe
3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
4 | */
5 | package de.blinkt.openvpn.core;
6 |
7 | import android.annotation.TargetApi;
8 | import android.content.Context;
9 | import android.content.Intent;
10 | import android.os.Build;
11 |
12 | import java.io.File;
13 | import java.io.FileOutputStream;
14 | import java.io.IOException;
15 | import java.io.InputStream;
16 | import java.util.Arrays;
17 | import java.util.Vector;
18 |
19 | import de.blinkt.openvpn.R;
20 | import de.blinkt.openvpn.VpnProfile;
21 |
22 | public class VPNLaunchHelper {
23 | private static final String MININONPIEVPN = "nopie_openvpn";
24 | private static final String MINIPIEVPN = "pie_openvpn";
25 | private static final String OVPNCONFIGFILE = "android.conf";
26 |
27 | private static String writeMiniVPN(Context context) {
28 | String[] abis;
29 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
30 | abis = getSupportedABIsLollipop();
31 | } else {
32 | //noinspection deprecation
33 | abis = new String[]{Build.CPU_ABI, Build.CPU_ABI2};
34 | }
35 | String nativeAPI = NativeUtils.getNativeAPI();
36 | if (!nativeAPI.equals(abis[0])) {
37 | VpnStatus.logWarning(R.string.abi_mismatch, Arrays.toString(abis), nativeAPI);
38 | abis = new String[]{nativeAPI};
39 | }
40 | for (String abi : abis) {
41 | File vpnExecutable = new File(context.getCacheDir(), getMiniVPNExecutableName() + "." + abi);
42 | if ((vpnExecutable.exists() && vpnExecutable.canExecute()) || writeMiniVPNBinary(context, abi, vpnExecutable)) {
43 | return vpnExecutable.getPath();
44 | }
45 | }
46 | return null;
47 | }
48 |
49 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
50 | private static String[] getSupportedABIsLollipop() {
51 | return Build.SUPPORTED_ABIS;
52 | }
53 |
54 | private static String getMiniVPNExecutableName() {
55 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) return MINIPIEVPN;
56 | else return MININONPIEVPN;
57 | }
58 |
59 | static String[] replacePieWithNoPie(String[] mArgv) {
60 | mArgv[0] = mArgv[0].replace(MINIPIEVPN, MININONPIEVPN);
61 | return mArgv;
62 | }
63 |
64 | static String[] buildOpenvpnArgv(Context c) {
65 | Vector args = new Vector<>();
66 | String binaryName = writeMiniVPN(c);
67 | // Add fixed paramenters
68 | //args.add("/data/data/de.blinkt.openvpn/lib/openvpn");
69 | if (binaryName == null) {
70 | VpnStatus.logError("Error writing minivpn binary");
71 | return null;
72 | }
73 | args.add(binaryName);
74 | args.add("--config");
75 | args.add(getConfigFilePath(c));
76 | return args.toArray(new String[args.size()]);
77 | }
78 |
79 | private static boolean writeMiniVPNBinary(Context context, String abi, File mvpnout) {
80 | try {
81 | InputStream mvpn;
82 | try {
83 | mvpn = context.getAssets().open(getMiniVPNExecutableName() + "." + abi);
84 | } catch (IOException errabi) {
85 | VpnStatus.logInfo("Failed getting assets for archicture " + abi);
86 | return false;
87 | }
88 | FileOutputStream fout = new FileOutputStream(mvpnout);
89 | byte buf[] = new byte[4096];
90 | int lenread = mvpn.read(buf);
91 | while (lenread > 0) {
92 | fout.write(buf, 0, lenread);
93 | lenread = mvpn.read(buf);
94 | }
95 | fout.close();
96 | if (!mvpnout.setExecutable(true)) {
97 | VpnStatus.logError("Failed to make OpenVPN executable");
98 | return false;
99 | }
100 | return true;
101 | } catch (IOException e) {
102 | VpnStatus.logException(e);
103 | return false;
104 | }
105 | }
106 |
107 | public static void startOpenVpn(VpnProfile startprofile, Context context) {
108 | Intent startVPN = startprofile.getStartServiceIntent(context);
109 | if (startVPN != null) {
110 | context.startService(startVPN);
111 | }
112 | }
113 |
114 | public static String getConfigFilePath(Context context) {
115 | return context.getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/vpn/src/main/java/de/blinkt/openvpn/core/VpnStatus.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2016 Arne Schwabe
3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
4 | */
5 | package de.blinkt.openvpn.core;
6 |
7 | import android.content.Context;
8 | import android.os.Build;
9 | import android.os.HandlerThread;
10 | import android.os.Message;
11 |
12 | import java.io.File;
13 | import java.io.PrintWriter;
14 | import java.io.StringWriter;
15 | import java.util.LinkedList;
16 | import java.util.Locale;
17 | import java.util.Vector;
18 |
19 | import de.blinkt.openvpn.R;
20 |
21 | public class VpnStatus {
22 | // keytool -printcert -jarfile de.blinkt.openvpn_85.apk
23 | public static final byte[] officalkey = {-58, -42, -44, -106, 90, -88, -87, -88, -52, -124, 84, 117, 66, 79, -112, -111, -46, 86, -37, 109};
24 | public static final byte[] officaldebugkey = {-99, -69, 45, 71, 114, -116, 82, 66, -99, -122, 50, -70, -56, -111, 98, -35, -65, 105, 82, 43};
25 | public static final byte[] amazonkey = {-116, -115, -118, -89, -116, -112, 120, 55, 79, -8, -119, -23, 106, -114, -85, -56, -4, 105, 26, -57};
26 | public static final byte[] fdroidkey = {-92, 111, -42, -46, 123, -96, -60, 79, -27, -31, 49, 103, 11, -54, -68, -27, 17, 2, 121, 104};
27 | static final int MAXLOGENTRIES = 1000;
28 | public static LinkedList logbuffer;
29 | private static Vector logListener;
30 | private static Vector stateListener;
31 | private static Vector byteCountListener;
32 | private static String mLaststatemsg = "";
33 | private static String mLaststate = "NOPROCESS";
34 | private static int mLastStateresid = R.string.state_noprocess;
35 | private static long mlastByteCount[] = {0, 0, 0, 0};
36 | private static HandlerThread mHandlerThread;
37 | private static ConnectionStatus mLastLevel = ConnectionStatus.LEVEL_NOTCONNECTED;
38 | private static LogFileHandler mLogFileHandler;
39 |
40 | static {
41 | logbuffer = new LinkedList<>();
42 | logListener = new Vector<>();
43 | stateListener = new Vector<>();
44 | byteCountListener = new Vector<>();
45 | logInformation();
46 | }
47 |
48 | public static void logException(LogLevel ll, String context, Exception e) {
49 | StringWriter sw = new StringWriter();
50 | e.printStackTrace(new PrintWriter(sw));
51 | LogItem li;
52 | if (context != null) {
53 | li = new LogItem(ll, R.string.unhandled_exception_context, e.getMessage(), sw.toString(), context);
54 | } else {
55 | li = new LogItem(ll, R.string.unhandled_exception, e.getMessage(), sw.toString());
56 | }
57 | newLogItem(li);
58 | }
59 |
60 | public static void logException(Exception e) {
61 | logException(LogLevel.ERROR, null, e);
62 | }
63 |
64 | static void logException(String context, Exception e) {
65 | logException(LogLevel.ERROR, context, e);
66 | }
67 |
68 | public static boolean isVPNActive() {
69 | return mLastLevel != ConnectionStatus.LEVEL_AUTH_FAILED && !(mLastLevel == ConnectionStatus.LEVEL_NOTCONNECTED);
70 | }
71 |
72 | static String getLastCleanLogMessage(Context c) {
73 | String message = mLaststatemsg;
74 | switch (mLastLevel) {
75 | case LEVEL_CONNECTED:
76 | String[] parts = mLaststatemsg.split(",");
77 | /*
78 | (a) the integer unix date/time,
79 | (b) the state name,
80 | 0 (c) optional descriptive string (used mostly on RECONNECTING
81 | and EXITING to show the reason for the disconnect),
82 | 1 (d) optional TUN/TAP local IPv4 address
83 | 2 (e) optional address of remote server,
84 | 3 (f) optional port of remote server,
85 | 4 (g) optional local address,
86 | 5 (h) optional local port, and
87 | 6 (i) optional TUN/TAP local IPv6 address.
88 | */
89 | // Return only the assigned IP addresses in the UI
90 | if (parts.length >= 7) {
91 | message = String.format(Locale.CHINA, "%s %s", parts[1], parts[6]);
92 | }
93 | break;
94 | }
95 | while (message.endsWith(",")) message = message.substring(0, message.length() - 1);
96 | String status = mLaststate;
97 | if (status.equals("NOPROCESS")) return message;
98 | if (mLastStateresid == R.string.state_waitconnectretry) {
99 | return c.getString(R.string.state_waitconnectretry, mLaststatemsg);
100 | }
101 | String prefix = c.getString(mLastStateresid);
102 | if (mLastStateresid == R.string.unknown_state) message = status + message;
103 | if (message.length() > 0) prefix += ": ";
104 | return prefix + message;
105 | }
106 |
107 | public static void initLogCache(File cacheDir) {
108 | mHandlerThread = new HandlerThread("LogFileWriter", Thread.MIN_PRIORITY);
109 | mHandlerThread.start();
110 | mLogFileHandler = new LogFileHandler(mHandlerThread.getLooper());
111 | Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_INIT, cacheDir);
112 | mLogFileHandler.sendMessage(m);
113 | }
114 |
115 | static void flushLog() {
116 | mLogFileHandler.sendEmptyMessage(LogFileHandler.FLUSH_TO_DISK);
117 | }
118 |
119 | public synchronized static void logMessage(LogLevel level, String prefix, String message) {
120 | newLogItem(new LogItem(level, prefix + message));
121 | }
122 |
123 | public synchronized static void clearLog() {
124 | logbuffer.clear();
125 | logInformation();
126 | if (mLogFileHandler != null) mLogFileHandler.sendEmptyMessage(LogFileHandler.TRIM_LOG_FILE);
127 | }
128 |
129 | private static void logInformation() {
130 | String nativeAPI;
131 | try {
132 | nativeAPI = NativeUtils.getNativeAPI();
133 | } catch (UnsatisfiedLinkError ignore) {
134 | nativeAPI = "error";
135 | }
136 | logInfo(R.string.mobile_info, Build.MODEL, Build.BOARD, Build.BRAND, Build.VERSION.SDK_INT, nativeAPI, Build.VERSION.RELEASE, Build.ID, Build.FINGERPRINT, "", "");
137 | }
138 |
139 | public synchronized static void addLogListener(LogListener ll) {
140 | logListener.add(ll);
141 | }
142 |
143 | public synchronized static void removeLogListener(LogListener ll) {
144 | logListener.remove(ll);
145 | }
146 |
147 | synchronized static void addByteCountListener(ByteCountListener bcl) {
148 | bcl.updateByteCount(mlastByteCount[0], mlastByteCount[1], mlastByteCount[2], mlastByteCount[3]);
149 | byteCountListener.add(bcl);
150 | }
151 |
152 | synchronized static void removeByteCountListener(ByteCountListener bcl) {
153 | byteCountListener.remove(bcl);
154 | }
155 |
156 | synchronized static void addStateListener(StateListener sl) {
157 | if (!stateListener.contains(sl)) {
158 | stateListener.add(sl);
159 | if (mLaststate != null) sl.updateState(mLaststate, mLaststatemsg, mLastStateresid, mLastLevel);
160 | }
161 | }
162 |
163 | private static int getLocalizedState(String state) {
164 | switch (state) {
165 | case "CONNECTING":
166 | return R.string.state_connecting;
167 | case "WAIT":
168 | return R.string.state_wait;
169 | case "AUTH":
170 | return R.string.state_auth;
171 | case "GET_CONFIG":
172 | return R.string.state_get_config;
173 | case "ASSIGN_IP":
174 | return R.string.state_assign_ip;
175 | case "ADD_ROUTES":
176 | return R.string.state_add_routes;
177 | case "CONNECTED":
178 | return R.string.state_connected;
179 | case "DISCONNECTED":
180 | return R.string.state_disconnected;
181 | case "RECONNECTING":
182 | return R.string.state_reconnecting;
183 | case "EXITING":
184 | return R.string.state_exiting;
185 | case "RESOLVE":
186 | return R.string.state_resolve;
187 | case "TCP_CONNECT":
188 | return R.string.state_tcp_connect;
189 | default:
190 | return R.string.unknown_state;
191 | }
192 | }
193 |
194 | static void updateStatePause(OpenVPNManagement.pauseReason pauseReason) {
195 | switch (pauseReason) {
196 | case noNetwork:
197 | VpnStatus.updateStateString("NONETWORK", "", R.string.state_nonetwork, ConnectionStatus.LEVEL_NONETWORK);
198 | break;
199 | case screenOff:
200 | VpnStatus.updateStateString("SCREENOFF", "", R.string.state_screenoff, ConnectionStatus.LEVEL_VPNPAUSED);
201 | break;
202 | case userPause:
203 | VpnStatus.updateStateString("USERPAUSE", "", R.string.state_userpause, ConnectionStatus.LEVEL_VPNPAUSED);
204 | break;
205 | }
206 | }
207 |
208 | private static ConnectionStatus getLevel(String state) {
209 | String[] noreplyet = {"CONNECTING", "WAIT", "RECONNECTING", "RESOLVE", "TCP_CONNECT"};
210 | String[] reply = {"AUTH", "GET_CONFIG", "ASSIGN_IP", "ADD_ROUTES"};
211 | String[] connected = {"CONNECTED"};
212 | String[] notconnected = {"DISCONNECTED", "EXITING"};
213 | for (String x : noreplyet)
214 | if (state.equals(x)) return ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET;
215 | for (String x : reply)
216 | if (state.equals(x)) return ConnectionStatus.LEVEL_CONNECTING_SERVER_REPLIED;
217 | for (String x : connected)
218 | if (state.equals(x)) return ConnectionStatus.LEVEL_CONNECTED;
219 | for (String x : notconnected)
220 | if (state.equals(x)) return ConnectionStatus.LEVEL_NOTCONNECTED;
221 | return ConnectionStatus.UNKNOWN_LEVEL;
222 | }
223 |
224 | synchronized static void removeStateListener(StateListener sl) {
225 | stateListener.remove(sl);
226 | }
227 |
228 | synchronized static LogItem[] getlogbuffer() {
229 | // The stoned way of java to return an array from a vector
230 | // brought to you by eclipse auto complete
231 | return logbuffer.toArray(new LogItem[logbuffer.size()]);
232 | }
233 |
234 | static void updateStateString(String state, String msg) {
235 | int rid = getLocalizedState(state);
236 | ConnectionStatus level = getLevel(state);
237 | updateStateString(state, msg, rid, level);
238 | }
239 |
240 | synchronized static void updateStateString(String state, String msg, int resid, ConnectionStatus level) {
241 | // Workound for OpenVPN doing AUTH and wait and being connected
242 | // Simply ignore these state
243 | if (mLastLevel == ConnectionStatus.LEVEL_CONNECTED && (state.equals("WAIT") || state.equals("AUTH"))) {
244 | newLogItem(new LogItem((LogLevel.DEBUG), String.format("Ignoring OpenVPN Status in CONNECTED state (%s->%s): %s", state, level.toString(), msg)));
245 | return;
246 | }
247 | mLaststate = state;
248 | mLaststatemsg = msg;
249 | mLastStateresid = resid;
250 | mLastLevel = level;
251 | for (StateListener sl : stateListener) {
252 | sl.updateState(state, msg, resid, level);
253 | }
254 | //newLogItem(new LogItem((LogLevel.DEBUG), String.format("New OpenVPN Status (%s->%s): %s",state,level.toString(),msg)));
255 | }
256 |
257 | static void logInfo(String message) {
258 | newLogItem(new LogItem(LogLevel.INFO, message));
259 | }
260 |
261 | static void logDebug(String message) {
262 | newLogItem(new LogItem(LogLevel.DEBUG, message));
263 | }
264 |
265 | static void logInfo(int resourceId, Object... args) {
266 | newLogItem(new LogItem(LogLevel.INFO, resourceId, args));
267 | }
268 |
269 | static void logDebug(int resourceId, Object... args) {
270 | newLogItem(new LogItem(LogLevel.DEBUG, resourceId, args));
271 | }
272 |
273 | private static void newLogItem(LogItem logItem) {
274 | newLogItem(logItem, false);
275 | }
276 |
277 | synchronized static void newLogItem(LogItem logItem, boolean cachedLine) {
278 | if (cachedLine) {
279 | logbuffer.addFirst(logItem);
280 | } else {
281 | logbuffer.addLast(logItem);
282 | if (mLogFileHandler != null) {
283 | Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_MESSAGE, logItem);
284 | mLogFileHandler.sendMessage(m);
285 | }
286 | }
287 | if (logbuffer.size() > MAXLOGENTRIES + MAXLOGENTRIES / 2) {
288 | while (logbuffer.size() > MAXLOGENTRIES) logbuffer.removeFirst();
289 | if (mLogFileHandler != null) mLogFileHandler.sendMessage(mLogFileHandler.obtainMessage(LogFileHandler.TRIM_LOG_FILE));
290 | }
291 | //if (BuildConfig.DEBUG && !cachedLine && !BuildConfig.FLAVOR.equals("test"))
292 | // Log.d("OpenVPN", logItem.getString(null));
293 | for (LogListener ll : logListener) {
294 | ll.newLog(logItem);
295 | }
296 | }
297 |
298 | public static void logError(String msg) {
299 | newLogItem(new LogItem(LogLevel.ERROR, msg));
300 | }
301 |
302 | static void logWarning(int resourceId, Object... args) {
303 | newLogItem(new LogItem(LogLevel.WARNING, resourceId, args));
304 | }
305 |
306 | static void logWarning(String msg) {
307 | newLogItem(new LogItem(LogLevel.WARNING, msg));
308 | }
309 |
310 | public static void logError(int resourceId) {
311 | newLogItem(new LogItem(LogLevel.ERROR, resourceId));
312 | }
313 |
314 | public static void logError(int resourceId, Object... args) {
315 | newLogItem(new LogItem(LogLevel.ERROR, resourceId, args));
316 | }
317 |
318 | static void logMessageOpenVPN(LogLevel level, int ovpnlevel, String message) {
319 | newLogItem(new LogItem(level, ovpnlevel, message));
320 | }
321 |
322 | static synchronized void updateByteCount(long in, long out) {
323 | long lastIn = mlastByteCount[0];
324 | long lastOut = mlastByteCount[1];
325 | long diffIn = mlastByteCount[2] = Math.max(0, in - lastIn);
326 | long diffOut = mlastByteCount[3] = Math.max(0, out - lastOut);
327 | mlastByteCount = new long[]{in, out, diffIn, diffOut};
328 | for (ByteCountListener bcl : byteCountListener) {
329 | bcl.updateByteCount(in, out, diffIn, diffOut);
330 | }
331 | }
332 |
333 | enum ConnectionStatus {
334 | LEVEL_CONNECTED,
335 | LEVEL_VPNPAUSED,
336 | LEVEL_CONNECTING_SERVER_REPLIED,
337 | LEVEL_CONNECTING_NO_SERVER_REPLY_YET,
338 | LEVEL_NONETWORK,
339 | LEVEL_NOTCONNECTED,
340 | LEVEL_START,
341 | LEVEL_AUTH_FAILED,
342 | LEVEL_WAITING_FOR_USER_INPUT,
343 | UNKNOWN_LEVEL
344 | }
345 |
346 | public enum LogLevel {
347 | INFO(2),
348 | ERROR(-2),
349 | WARNING(1),
350 | VERBOSE(3),
351 | DEBUG(4);
352 | protected int mValue;
353 |
354 | LogLevel(int value) {
355 | mValue = value;
356 | }
357 |
358 | public static LogLevel getEnumByValue(int value) {
359 | switch (value) {
360 | case 1:
361 | return INFO;
362 | case 2:
363 | return ERROR;
364 | case 3:
365 | return WARNING;
366 | case 4:
367 | return DEBUG;
368 | default:
369 | return null;
370 | }
371 | }
372 |
373 | public int getInt() {
374 | return mValue;
375 | }
376 | }
377 |
378 | interface LogListener {
379 | void newLog(LogItem logItem);
380 | }
381 |
382 | interface StateListener {
383 | void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level);
384 | }
385 |
386 | interface ByteCountListener {
387 | void updateByteCount(long in, long out, long diffIn, long diffOut);
388 | }
389 | }
390 |
--------------------------------------------------------------------------------
/vpn/src/main/java/de/blinkt/openvpn/core/X509Utils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2016 Arne Schwabe
3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
4 | */
5 | package de.blinkt.openvpn.core;
6 |
7 | import android.content.Context;
8 | import android.content.res.Resources;
9 | import android.text.TextUtils;
10 |
11 | import org.spongycastle.util.io.pem.PemObject;
12 | import org.spongycastle.util.io.pem.PemReader;
13 |
14 | import java.io.ByteArrayInputStream;
15 | import java.io.File;
16 | import java.io.FileInputStream;
17 | import java.io.FileNotFoundException;
18 | import java.io.FileReader;
19 | import java.io.IOException;
20 | import java.io.InputStream;
21 | import java.io.Reader;
22 | import java.io.StringReader;
23 | import java.lang.reflect.InvocationTargetException;
24 | import java.lang.reflect.Method;
25 | import java.security.cert.Certificate;
26 | import java.security.cert.CertificateException;
27 | import java.security.cert.CertificateExpiredException;
28 | import java.security.cert.CertificateFactory;
29 | import java.security.cert.CertificateNotYetValidException;
30 | import java.security.cert.X509Certificate;
31 | import java.util.Date;
32 | import java.util.Hashtable;
33 | import java.util.Vector;
34 |
35 | import javax.security.auth.x500.X500Principal;
36 |
37 | import de.blinkt.openvpn.R;
38 | import de.blinkt.openvpn.VpnProfile;
39 |
40 | public class X509Utils {
41 | public static Certificate[] getCertificatesFromFile(String certfilename) throws FileNotFoundException, CertificateException {
42 | CertificateFactory certFact = CertificateFactory.getInstance("X.509");
43 | Vector certificates = new Vector<>();
44 | if (VpnProfile.isEmbedded(certfilename)) {
45 | int subIndex = certfilename.indexOf("-----BEGIN CERTIFICATE-----");
46 | do {
47 | // The java certifcate reader is ... kind of stupid
48 | // It does NOT ignore chars before the --BEGIN ...
49 | subIndex = Math.max(0, subIndex);
50 | InputStream inStream = new ByteArrayInputStream(certfilename.substring(subIndex).getBytes());
51 | certificates.add(certFact.generateCertificate(inStream));
52 | subIndex = certfilename.indexOf("-----BEGIN CERTIFICATE-----", subIndex + 1);
53 | } while (subIndex > 0);
54 | return certificates.toArray(new Certificate[certificates.size()]);
55 | } else {
56 | InputStream inStream = new FileInputStream(certfilename);
57 | return new Certificate[]{certFact.generateCertificate(inStream)};
58 | }
59 | }
60 |
61 | public static PemObject readPemObjectFromFile(String keyfilename) throws IOException {
62 | Reader inStream;
63 | if (VpnProfile.isEmbedded(keyfilename)) inStream = new StringReader(VpnProfile.getEmbeddedContent(keyfilename));
64 | else inStream = new FileReader(new File(keyfilename));
65 | PemReader pr = new PemReader(inStream);
66 | PemObject r = pr.readPemObject();
67 | pr.close();
68 | return r;
69 | }
70 |
71 | public static String getCertificateFriendlyName(Context c, String filename) {
72 | if (!TextUtils.isEmpty(filename)) {
73 | try {
74 | X509Certificate cert = (X509Certificate) getCertificatesFromFile(filename)[0];
75 | String friendlycn = getCertificateFriendlyName(cert);
76 | friendlycn = getCertificateValidityString(cert, c.getResources()) + friendlycn;
77 | return friendlycn;
78 | } catch (Exception e) {
79 | VpnStatus.logError("Could not read certificate" + e.getLocalizedMessage());
80 | }
81 | }
82 | return c.getString(R.string.cannotparsecert);
83 | }
84 |
85 | public static String getCertificateValidityString(X509Certificate cert, Resources res) {
86 | try {
87 | cert.checkValidity();
88 | } catch (CertificateExpiredException ce) {
89 | return "EXPIRED: ";
90 | } catch (CertificateNotYetValidException cny) {
91 | return "NOT YET VALID: ";
92 | }
93 | Date certNotAfter = cert.getNotAfter();
94 | Date now = new Date();
95 | long timeLeft = certNotAfter.getTime() - now.getTime(); // Time left in ms
96 | // More than 72h left, display days
97 | // More than 3 months display months
98 | if (timeLeft > 90l * 24 * 3600 * 1000) {
99 | long months = getMonthsDifference(now, certNotAfter);
100 | return res.getString(R.string.months_left, months);
101 | } else if (timeLeft > 72 * 3600 * 1000) {
102 | long days = timeLeft / (24 * 3600 * 1000);
103 | return res.getString(R.string.days_left, days);
104 | } else {
105 | long hours = timeLeft / (3600 * 1000);
106 | return res.getString(R.string.hours_left, hours);
107 | }
108 | }
109 |
110 | public static int getMonthsDifference(Date date1, Date date2) {
111 | int m1 = date1.getYear() * 12 + date1.getMonth();
112 | int m2 = date2.getYear() * 12 + date2.getMonth();
113 | return m2 - m1 + 1;
114 | }
115 |
116 | public static String getCertificateFriendlyName(X509Certificate cert) {
117 | X500Principal principal = cert.getSubjectX500Principal();
118 | byte[] encodedSubject = principal.getEncoded();
119 | String friendlyName = null;
120 | /* Hack so we do not have to ship a whole Spongy/bouncycastle */
121 | Exception exp = null;
122 | try {
123 | Class X509NameClass = Class.forName("com.android.org.bouncycastle.asn1.x509.X509Name");
124 | Method getInstance = X509NameClass.getMethod("getInstance", Object.class);
125 | Hashtable defaultSymbols = (Hashtable) X509NameClass.getField("DefaultSymbols").get(X509NameClass);
126 | if (!defaultSymbols.containsKey("1.2.840.113549.1.9.1")) defaultSymbols.put("1.2.840.113549.1.9.1", "eMail");
127 | Object subjectName = getInstance.invoke(X509NameClass, encodedSubject);
128 | Method toString = X509NameClass.getMethod("toString", boolean.class, Hashtable.class);
129 | friendlyName = (String) toString.invoke(subjectName, true, defaultSymbols);
130 | } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException | NoSuchFieldException e) {
131 | exp = e;
132 | }
133 | if (exp != null) VpnStatus.logException("Getting X509 Name from certificate", exp);
134 | /* Fallback if the reflection method did not work */
135 | if (friendlyName == null) friendlyName = principal.getName();
136 | // Really evil hack to decode email address
137 | // See: http://code.google.com/p/android/issues/detail?id=21531
138 | String[] parts = friendlyName.split(",");
139 | for (int i = 0; i < parts.length; i++) {
140 | String part = parts[i];
141 | if (part.startsWith("1.2.840.113549.1.9.1=#16")) {
142 | parts[i] = "email=" + ia5decode(part.replace("1.2.840.113549.1.9.1=#16", ""));
143 | }
144 | }
145 | friendlyName = TextUtils.join(",", parts);
146 | return friendlyName;
147 | }
148 |
149 | private static boolean isPrintableChar(char c) {
150 | Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
151 | return (!Character.isISOControl(c)) && block != null && block != Character.UnicodeBlock.SPECIALS;
152 | }
153 |
154 | private static String ia5decode(String ia5string) {
155 | String d = "";
156 | for (int i = 1; i < ia5string.length(); i = i + 2) {
157 | String hexstr = ia5string.substring(i - 1, i + 1);
158 | char c = (char) Integer.parseInt(hexstr, 16);
159 | if (isPrintableChar(c)) {
160 | d += c;
161 | } else if (i == 1 && (c == 0x12 || c == 0x1b)) {
162 | ; // ignore
163 | } else {
164 | d += "\\x" + hexstr;
165 | }
166 | }
167 | return d;
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/vpn/src/main/java/org/spongycastle/util/encoders/Base64.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2016 Arne Schwabe
3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
4 | */
5 |
6 | package org.spongycastle.util.encoders;
7 |
8 | import java.io.ByteArrayOutputStream;
9 | import java.io.IOException;
10 | import java.io.OutputStream;
11 |
12 | public class Base64 {
13 | private static final Encoder encoder = new Base64Encoder();
14 |
15 | /**
16 | * encode the input data producing a base 64 encoded byte array.
17 | *
18 | * @return a byte array containing the base 64 encoded data.
19 | */
20 | public static byte[] encode(byte[] data) {
21 | int len = (data.length + 2) / 3 * 4;
22 | ByteArrayOutputStream bOut = new ByteArrayOutputStream(len);
23 |
24 | try {
25 | encoder.encode(data, 0, data.length, bOut);
26 | } catch (IOException e) {
27 | throw new RuntimeException("exception encoding base64 string: " + e);
28 | }
29 |
30 | return bOut.toByteArray();
31 | }
32 |
33 | /**
34 | * Encode the byte data to base 64 writing it to the given output stream.
35 | *
36 | * @return the number of bytes produced.
37 | */
38 | public static int encode(byte[] data, OutputStream out) throws IOException {
39 | return encoder.encode(data, 0, data.length, out);
40 | }
41 |
42 | /**
43 | * Encode the byte data to base 64 writing it to the given output stream.
44 | *
45 | * @return the number of bytes produced.
46 | */
47 | public static int encode(byte[] data, int off, int length, OutputStream out) throws IOException {
48 | return encoder.encode(data, off, length, out);
49 | }
50 |
51 | /**
52 | * decode the base 64 encoded input data. It is assumed the input data is valid.
53 | *
54 | * @return a byte array representing the decoded data.
55 | */
56 | public static byte[] decode(byte[] data) {
57 | int len = data.length / 4 * 3;
58 | ByteArrayOutputStream bOut = new ByteArrayOutputStream(len);
59 |
60 | try {
61 | encoder.decode(data, 0, data.length, bOut);
62 | } catch (IOException e) {
63 | throw new RuntimeException("exception decoding base64 string: " + e);
64 | }
65 |
66 | return bOut.toByteArray();
67 | }
68 |
69 | /**
70 | * decode the base 64 encoded String data - whitespace will be ignored.
71 | *
72 | * @return a byte array representing the decoded data.
73 | */
74 | public static byte[] decode(String data) {
75 | int len = data.length() / 4 * 3;
76 | ByteArrayOutputStream bOut = new ByteArrayOutputStream(len);
77 |
78 | try {
79 | encoder.decode(data, bOut);
80 | } catch (IOException e) {
81 | throw new RuntimeException("exception decoding base64 string: " + e);
82 | }
83 |
84 | return bOut.toByteArray();
85 | }
86 |
87 | /**
88 | * decode the base 64 encoded String data writing it to the given output stream,
89 | * whitespace characters will be ignored.
90 | *
91 | * @return the number of bytes produced.
92 | */
93 | public static int decode(String data, OutputStream out) throws IOException {
94 | return encoder.decode(data, out);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/vpn/src/main/java/org/spongycastle/util/encoders/Base64Encoder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2016 Arne Schwabe
3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
4 | */
5 |
6 | package org.spongycastle.util.encoders;
7 |
8 | import java.io.IOException;
9 | import java.io.OutputStream;
10 |
11 | public class Base64Encoder implements Encoder {
12 | protected final byte[] encodingTable = {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '+', (byte) '/'};
13 |
14 | protected byte padding = (byte) '=';
15 |
16 | /*
17 | * set up the decoding table.
18 | */
19 | protected final byte[] decodingTable = new byte[128];
20 |
21 | protected void initialiseDecodingTable() {
22 | for (int i = 0; i < encodingTable.length; i++) {
23 | decodingTable[encodingTable[i]] = (byte) i;
24 | }
25 | }
26 |
27 | public Base64Encoder() {
28 | initialiseDecodingTable();
29 | }
30 |
31 | /**
32 | * encode the input data producing a base 64 output stream.
33 | *
34 | * @return the number of bytes produced.
35 | */
36 | public int encode(byte[] data, int off, int length, OutputStream out) throws IOException {
37 | int modulus = length % 3;
38 | int dataLength = (length - modulus);
39 | int a1, a2, a3;
40 |
41 | for (int i = off; i < off + dataLength; i += 3) {
42 | a1 = data[i] & 0xff;
43 | a2 = data[i + 1] & 0xff;
44 | a3 = data[i + 2] & 0xff;
45 |
46 | out.write(encodingTable[(a1 >>> 2) & 0x3f]);
47 | out.write(encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]);
48 | out.write(encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]);
49 | out.write(encodingTable[a3 & 0x3f]);
50 | }
51 |
52 | /*
53 | * process the tail end.
54 | */
55 | int b1, b2, b3;
56 | int d1, d2;
57 |
58 | switch (modulus) {
59 | case 0: /* nothing left to do */
60 | break;
61 | case 1:
62 | d1 = data[off + dataLength] & 0xff;
63 | b1 = (d1 >>> 2) & 0x3f;
64 | b2 = (d1 << 4) & 0x3f;
65 |
66 | out.write(encodingTable[b1]);
67 | out.write(encodingTable[b2]);
68 | out.write(padding);
69 | out.write(padding);
70 | break;
71 | case 2:
72 | d1 = data[off + dataLength] & 0xff;
73 | d2 = data[off + dataLength + 1] & 0xff;
74 |
75 | b1 = (d1 >>> 2) & 0x3f;
76 | b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f;
77 | b3 = (d2 << 2) & 0x3f;
78 |
79 | out.write(encodingTable[b1]);
80 | out.write(encodingTable[b2]);
81 | out.write(encodingTable[b3]);
82 | out.write(padding);
83 | break;
84 | }
85 |
86 | return (dataLength / 3) * 4 + ((modulus == 0) ? 0 : 4);
87 | }
88 |
89 | private boolean ignore(char c) {
90 | return (c == '\n' || c == '\r' || c == '\t' || c == ' ');
91 | }
92 |
93 | /**
94 | * decode the base 64 encoded byte data writing it to the given output stream,
95 | * whitespace characters will be ignored.
96 | *
97 | * @return the number of bytes produced.
98 | */
99 | public int decode(byte[] data, int off, int length, OutputStream out) throws IOException {
100 | byte b1, b2, b3, b4;
101 | int outLen = 0;
102 |
103 | int end = off + length;
104 |
105 | while (end > off) {
106 | if (!ignore((char) data[end - 1])) {
107 | break;
108 | }
109 |
110 | end--;
111 | }
112 |
113 | int i = off;
114 | int finish = end - 4;
115 |
116 | i = nextI(data, i, finish);
117 |
118 | while (i < finish) {
119 | b1 = decodingTable[data[i++]];
120 |
121 | i = nextI(data, i, finish);
122 |
123 | b2 = decodingTable[data[i++]];
124 |
125 | i = nextI(data, i, finish);
126 |
127 | b3 = decodingTable[data[i++]];
128 |
129 | i = nextI(data, i, finish);
130 |
131 | b4 = decodingTable[data[i++]];
132 |
133 | out.write((b1 << 2) | (b2 >> 4));
134 | out.write((b2 << 4) | (b3 >> 2));
135 | out.write((b3 << 6) | b4);
136 |
137 | outLen += 3;
138 |
139 | i = nextI(data, i, finish);
140 | }
141 |
142 | outLen += decodeLastBlock(out, (char) data[end - 4], (char) data[end - 3], (char) data[end - 2], (char) data[end - 1]);
143 |
144 | return outLen;
145 | }
146 |
147 | private int nextI(byte[] data, int i, int finish) {
148 | while ((i < finish) && ignore((char) data[i])) {
149 | i++;
150 | }
151 | return i;
152 | }
153 |
154 | /**
155 | * decode the base 64 encoded String data writing it to the given output stream,
156 | * whitespace characters will be ignored.
157 | *
158 | * @return the number of bytes produced.
159 | */
160 | public int decode(String data, OutputStream out) throws IOException {
161 | byte b1, b2, b3, b4;
162 | int length = 0;
163 |
164 | int end = data.length();
165 |
166 | while (end > 0) {
167 | if (!ignore(data.charAt(end - 1))) {
168 | break;
169 | }
170 |
171 | end--;
172 | }
173 |
174 | int i = 0;
175 | int finish = end - 4;
176 |
177 | i = nextI(data, i, finish);
178 |
179 | while (i < finish) {
180 | b1 = decodingTable[data.charAt(i++)];
181 |
182 | i = nextI(data, i, finish);
183 |
184 | b2 = decodingTable[data.charAt(i++)];
185 |
186 | i = nextI(data, i, finish);
187 |
188 | b3 = decodingTable[data.charAt(i++)];
189 |
190 | i = nextI(data, i, finish);
191 |
192 | b4 = decodingTable[data.charAt(i++)];
193 |
194 | out.write((b1 << 2) | (b2 >> 4));
195 | out.write((b2 << 4) | (b3 >> 2));
196 | out.write((b3 << 6) | b4);
197 |
198 | length += 3;
199 |
200 | i = nextI(data, i, finish);
201 | }
202 |
203 | length += decodeLastBlock(out, data.charAt(end - 4), data.charAt(end - 3), data.charAt(end - 2), data.charAt(end - 1));
204 |
205 | return length;
206 | }
207 |
208 | private int decodeLastBlock(OutputStream out, char c1, char c2, char c3, char c4) throws IOException {
209 | byte b1, b2, b3, b4;
210 |
211 | if (c3 == padding) {
212 | b1 = decodingTable[c1];
213 | b2 = decodingTable[c2];
214 |
215 | out.write((b1 << 2) | (b2 >> 4));
216 |
217 | return 1;
218 | } else if (c4 == padding) {
219 | b1 = decodingTable[c1];
220 | b2 = decodingTable[c2];
221 | b3 = decodingTable[c3];
222 |
223 | out.write((b1 << 2) | (b2 >> 4));
224 | out.write((b2 << 4) | (b3 >> 2));
225 |
226 | return 2;
227 | } else {
228 | b1 = decodingTable[c1];
229 | b2 = decodingTable[c2];
230 | b3 = decodingTable[c3];
231 | b4 = decodingTable[c4];
232 |
233 | out.write((b1 << 2) | (b2 >> 4));
234 | out.write((b2 << 4) | (b3 >> 2));
235 | out.write((b3 << 6) | b4);
236 |
237 | return 3;
238 | }
239 | }
240 |
241 | private int nextI(String data, int i, int finish) {
242 | while ((i < finish) && ignore(data.charAt(i))) {
243 | i++;
244 | }
245 | return i;
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/vpn/src/main/java/org/spongycastle/util/encoders/Encoder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2016 Arne Schwabe
3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
4 | */
5 |
6 | package org.spongycastle.util.encoders;
7 |
8 | import java.io.IOException;
9 | import java.io.OutputStream;
10 |
11 | /**
12 | * Encode and decode byte arrays (typically from binary to 7-bit ASCII
13 | * encodings).
14 | */
15 | public interface Encoder
16 | {
17 | int encode(byte[] data, int off, int length, OutputStream out) throws IOException;
18 |
19 | int decode(byte[] data, int off, int length, OutputStream out) throws IOException;
20 |
21 | int decode(String data, OutputStream out) throws IOException;
22 | }
23 |
--------------------------------------------------------------------------------
/vpn/src/main/java/org/spongycastle/util/io/pem/PemGenerationException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2016 Arne Schwabe
3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
4 | */
5 |
6 | package org.spongycastle.util.io.pem;
7 |
8 | import java.io.IOException;
9 |
10 | @SuppressWarnings("serial")
11 | public class PemGenerationException
12 | extends IOException
13 | {
14 | private Throwable cause;
15 |
16 | public PemGenerationException(String message, Throwable cause)
17 | {
18 | super(message);
19 | this.cause = cause;
20 | }
21 |
22 | public PemGenerationException(String message)
23 | {
24 | super(message);
25 | }
26 |
27 | public Throwable getCause()
28 | {
29 | return cause;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/vpn/src/main/java/org/spongycastle/util/io/pem/PemHeader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2016 Arne Schwabe
3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
4 | */
5 |
6 | package org.spongycastle.util.io.pem;
7 |
8 | public class PemHeader
9 | {
10 | private String name;
11 | private String value;
12 |
13 | public PemHeader(String name, String value)
14 | {
15 | this.name = name;
16 | this.value = value;
17 | }
18 |
19 | public String getName()
20 | {
21 | return name;
22 | }
23 |
24 | public String getValue()
25 | {
26 | return value;
27 | }
28 |
29 | public int hashCode()
30 | {
31 | return getHashCode(this.name) + 31 * getHashCode(this.value);
32 | }
33 |
34 | public boolean equals(Object o)
35 | {
36 | if (!(o instanceof PemHeader))
37 | {
38 | return false;
39 | }
40 |
41 | PemHeader other = (PemHeader)o;
42 |
43 | return other == this || (isEqual(this.name, other.name) && isEqual(this.value, other.value));
44 | }
45 |
46 | private int getHashCode(String s)
47 | {
48 | if (s == null)
49 | {
50 | return 1;
51 | }
52 |
53 | return s.hashCode();
54 | }
55 |
56 | private boolean isEqual(String s1, String s2)
57 | {
58 | if (s1 == s2)
59 | {
60 | return true;
61 | }
62 |
63 | if (s1 == null || s2 == null)
64 | {
65 | return false;
66 | }
67 |
68 | return s1.equals(s2);
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/vpn/src/main/java/org/spongycastle/util/io/pem/PemObject.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2016 Arne Schwabe
3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
4 | */
5 |
6 | package org.spongycastle.util.io.pem;
7 |
8 | import java.util.ArrayList;
9 | import java.util.Collections;
10 | import java.util.List;
11 |
12 | @SuppressWarnings("all")
13 | public class PemObject
14 | implements PemObjectGenerator
15 | {
16 | private static final List EMPTY_LIST = Collections.unmodifiableList(new ArrayList());
17 |
18 | private String type;
19 | private List headers;
20 | private byte[] content;
21 |
22 | /**
23 | * Generic constructor for object without headers.
24 | *
25 | * @param type pem object type.
26 | * @param content the binary content of the object.
27 | */
28 | public PemObject(String type, byte[] content)
29 | {
30 | this(type, EMPTY_LIST, content);
31 | }
32 |
33 | /**
34 | * Generic constructor for object with headers.
35 | *
36 | * @param type pem object type.
37 | * @param headers a list of PemHeader objects.
38 | * @param content the binary content of the object.
39 | */
40 | public PemObject(String type, List headers, byte[] content)
41 | {
42 | this.type = type;
43 | this.headers = Collections.unmodifiableList(headers);
44 | this.content = content;
45 | }
46 |
47 | public String getType()
48 | {
49 | return type;
50 | }
51 |
52 | public List getHeaders()
53 | {
54 | return headers;
55 | }
56 |
57 | public byte[] getContent()
58 | {
59 | return content;
60 | }
61 |
62 | public PemObject generate()
63 | throws PemGenerationException
64 | {
65 | return this;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/vpn/src/main/java/org/spongycastle/util/io/pem/PemObjectGenerator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2016 Arne Schwabe
3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
4 | */
5 |
6 | package org.spongycastle.util.io.pem;
7 |
8 | public interface PemObjectGenerator
9 | {
10 | PemObject generate()
11 | throws PemGenerationException;
12 | }
13 |
--------------------------------------------------------------------------------
/vpn/src/main/java/org/spongycastle/util/io/pem/PemReader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2016 Arne Schwabe
3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
4 | */
5 |
6 | package org.spongycastle.util.io.pem;
7 |
8 | import java.io.BufferedReader;
9 | import java.io.IOException;
10 | import java.io.Reader;
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | import org.spongycastle.util.encoders.Base64;
15 |
16 | public class PemReader extends BufferedReader {
17 | private static final String BEGIN = "-----BEGIN ";
18 | private static final String END = "-----END ";
19 |
20 | public PemReader(Reader reader) {
21 | super(reader);
22 | }
23 |
24 | public PemObject readPemObject() throws IOException {
25 | String line = readLine();
26 |
27 | while (line != null && !line.startsWith(BEGIN)) {
28 | line = readLine();
29 | }
30 |
31 | if (line != null) {
32 | line = line.substring(BEGIN.length());
33 | int index = line.indexOf('-');
34 | String type = line.substring(0, index);
35 |
36 | if (index > 0) {
37 | return loadObject(type);
38 | }
39 | }
40 |
41 | return null;
42 | }
43 |
44 | private PemObject loadObject(String type) throws IOException {
45 | String line;
46 | String endMarker = END + type;
47 | StringBuilder buf = new StringBuilder();
48 | List headers = new ArrayList();
49 |
50 | while ((line = readLine()) != null) {
51 | if (line.indexOf(":") >= 0) {
52 | int index = line.indexOf(':');
53 | String hdr = line.substring(0, index);
54 | String value = line.substring(index + 1).trim();
55 |
56 | headers.add(new PemHeader(hdr, value));
57 |
58 | continue;
59 | }
60 |
61 | if (line.indexOf(endMarker) != -1) {
62 | break;
63 | }
64 |
65 | buf.append(line.trim());
66 | }
67 |
68 | if (line == null) {
69 | throw new IOException(endMarker + " not found");
70 | }
71 |
72 | return new PemObject(type, headers, Base64.decode(buf.toString()));
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/vpn/src/main/java/org/spongycastle/util/io/pem/PemWriter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2016 Arne Schwabe
3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
4 | */
5 |
6 | package org.spongycastle.util.io.pem;
7 |
8 | import java.io.BufferedWriter;
9 | import java.io.IOException;
10 | import java.io.Writer;
11 | import java.util.Iterator;
12 |
13 | import org.spongycastle.util.encoders.Base64;
14 |
15 | /**
16 | * A generic PEM writer, based on RFC 1421
17 | */
18 | @SuppressWarnings("all")
19 | public class PemWriter
20 | extends BufferedWriter
21 | {
22 | private static final int LINE_LENGTH = 64;
23 |
24 | private final int nlLength;
25 | private char[] buf = new char[LINE_LENGTH];
26 |
27 | /**
28 | * Base constructor.
29 | *
30 | * @param out output stream to use.
31 | */
32 | public PemWriter(Writer out)
33 | {
34 | super(out);
35 |
36 | String nl = System.getProperty("line.separator");
37 | if (nl != null)
38 | {
39 | nlLength = nl.length();
40 | }
41 | else
42 | {
43 | nlLength = 2;
44 | }
45 | }
46 |
47 | /**
48 | * Return the number of bytes or characters required to contain the
49 | * passed in object if it is PEM encoded.
50 | *
51 | * @param obj pem object to be output
52 | * @return an estimate of the number of bytes
53 | */
54 | public int getOutputSize(PemObject obj)
55 | {
56 | // BEGIN and END boundaries.
57 | int size = (2 * (obj.getType().length() + 10 + nlLength)) + 6 + 4;
58 |
59 | if (!obj.getHeaders().isEmpty())
60 | {
61 | for (Iterator it = obj.getHeaders().iterator(); it.hasNext();)
62 | {
63 | PemHeader hdr = (PemHeader)it.next();
64 |
65 | size += hdr.getName().length() + ": ".length() + hdr.getValue().length() + nlLength;
66 | }
67 |
68 | size += nlLength;
69 | }
70 |
71 | // base64 encoding
72 | int dataLen = ((obj.getContent().length + 2) / 3) * 4;
73 |
74 | size += dataLen + (((dataLen + LINE_LENGTH - 1) / LINE_LENGTH) * nlLength);
75 |
76 | return size;
77 | }
78 |
79 | public void writeObject(PemObjectGenerator objGen)
80 | throws IOException
81 | {
82 | PemObject obj = objGen.generate();
83 |
84 | writePreEncapsulationBoundary(obj.getType());
85 |
86 | if (!obj.getHeaders().isEmpty())
87 | {
88 | for (Iterator it = obj.getHeaders().iterator(); it.hasNext();)
89 | {
90 | PemHeader hdr = (PemHeader)it.next();
91 |
92 | this.write(hdr.getName());
93 | this.write(": ");
94 | this.write(hdr.getValue());
95 | this.newLine();
96 | }
97 |
98 | this.newLine();
99 | }
100 |
101 | writeEncoded(obj.getContent());
102 | writePostEncapsulationBoundary(obj.getType());
103 | }
104 |
105 | private void writeEncoded(byte[] bytes)
106 | throws IOException
107 | {
108 | bytes = Base64.encode(bytes);
109 |
110 | for (int i = 0; i < bytes.length; i += buf.length)
111 | {
112 | int index = 0;
113 |
114 | while (index != buf.length)
115 | {
116 | if ((i + index) >= bytes.length)
117 | {
118 | break;
119 | }
120 | buf[index] = (char)bytes[i + index];
121 | index++;
122 | }
123 | this.write(buf, 0, index);
124 | this.newLine();
125 | }
126 | }
127 |
128 | private void writePreEncapsulationBoundary(
129 | String type)
130 | throws IOException
131 | {
132 | this.write("-----BEGIN " + type + "-----");
133 | this.newLine();
134 | }
135 |
136 | private void writePostEncapsulationBoundary(
137 | String type)
138 | throws IOException
139 | {
140 | this.write("-----END " + type + "-----");
141 | this.newLine();
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/vpn/src/main/jniLibs/arm64-v8a/libjbcrypto.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/arm64-v8a/libjbcrypto.so
--------------------------------------------------------------------------------
/vpn/src/main/jniLibs/arm64-v8a/libopenvpn.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/arm64-v8a/libopenvpn.so
--------------------------------------------------------------------------------
/vpn/src/main/jniLibs/arm64-v8a/libopvpnutil.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/arm64-v8a/libopvpnutil.so
--------------------------------------------------------------------------------
/vpn/src/main/jniLibs/armeabi-v7a/libjbcrypto.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/armeabi-v7a/libjbcrypto.so
--------------------------------------------------------------------------------
/vpn/src/main/jniLibs/armeabi-v7a/libopenvpn.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/armeabi-v7a/libopenvpn.so
--------------------------------------------------------------------------------
/vpn/src/main/jniLibs/armeabi-v7a/libopvpnutil.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/armeabi-v7a/libopvpnutil.so
--------------------------------------------------------------------------------
/vpn/src/main/jniLibs/armeabi/libjbcrypto.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/armeabi/libjbcrypto.so
--------------------------------------------------------------------------------
/vpn/src/main/jniLibs/armeabi/libopenvpn.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/armeabi/libopenvpn.so
--------------------------------------------------------------------------------
/vpn/src/main/jniLibs/armeabi/libopvpnutil.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/armeabi/libopvpnutil.so
--------------------------------------------------------------------------------
/vpn/src/main/jniLibs/mips/libjbcrypto.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/mips/libjbcrypto.so
--------------------------------------------------------------------------------
/vpn/src/main/jniLibs/mips/libopenvpn.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/mips/libopenvpn.so
--------------------------------------------------------------------------------
/vpn/src/main/jniLibs/mips/libopvpnutil.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/mips/libopvpnutil.so
--------------------------------------------------------------------------------
/vpn/src/main/jniLibs/x86/libjbcrypto.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/x86/libjbcrypto.so
--------------------------------------------------------------------------------
/vpn/src/main/jniLibs/x86/libopenvpn.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/x86/libopenvpn.so
--------------------------------------------------------------------------------
/vpn/src/main/jniLibs/x86/libopvpnutil.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/x86/libopvpnutil.so
--------------------------------------------------------------------------------
/vpn/src/main/jniLibs/x86_64/libjbcrypto.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/x86_64/libjbcrypto.so
--------------------------------------------------------------------------------
/vpn/src/main/jniLibs/x86_64/libopenvpn.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/x86_64/libopenvpn.so
--------------------------------------------------------------------------------
/vpn/src/main/jniLibs/x86_64/libopvpnutil.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/x86_64/libopvpnutil.so
--------------------------------------------------------------------------------
/vpn/src/main/res/drawable-hdpi/ic_menu_archive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/drawable-hdpi/ic_menu_archive.png
--------------------------------------------------------------------------------
/vpn/src/main/res/drawable-hdpi/ic_menu_copy_holo_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/drawable-hdpi/ic_menu_copy_holo_light.png
--------------------------------------------------------------------------------
/vpn/src/main/res/drawable-hdpi/ic_menu_log.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/drawable-hdpi/ic_menu_log.png
--------------------------------------------------------------------------------
/vpn/src/main/res/drawable-hdpi/ic_stat_vpn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/drawable-hdpi/ic_stat_vpn.png
--------------------------------------------------------------------------------
/vpn/src/main/res/drawable-hdpi/ic_stat_vpn_empty_halo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/drawable-hdpi/ic_stat_vpn_empty_halo.png
--------------------------------------------------------------------------------
/vpn/src/main/res/drawable-hdpi/ic_stat_vpn_offline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/drawable-hdpi/ic_stat_vpn_offline.png
--------------------------------------------------------------------------------
/vpn/src/main/res/drawable-hdpi/ic_stat_vpn_outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/drawable-hdpi/ic_stat_vpn_outline.png
--------------------------------------------------------------------------------
/vpn/src/main/res/drawable-hdpi/vpn_item_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/drawable-hdpi/vpn_item_settings.png
--------------------------------------------------------------------------------
/vpn/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
18 |
19 |
--------------------------------------------------------------------------------
/vpn/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/vpn/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/vpn/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/vpn/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/vpn/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/vpn/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/vpn/src/main/res/values/refs.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | @android:drawable/ic_menu_close_clear_cancel
9 | @android:drawable/ic_media_play
10 | @android:drawable/ic_media_pause
11 |
--------------------------------------------------------------------------------
/vpn/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
14 |
18 |
--------------------------------------------------------------------------------
/vpn/src/test/java/de/blinkt/openvpn/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package de.blinkt.openvpn;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------