-
Notifications
You must be signed in to change notification settings - Fork 81
/
JNLPClassLoader.java
2141 lines (1817 loc) · 81.9 KB
/
JNLPClassLoader.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
package net.sourceforge.jnlp.runtime.classloader;
import net.adoptopenjdk.icedteaweb.commandline.CommandLineOptions;
import net.adoptopenjdk.icedteaweb.http.CloseableConnection;
import net.adoptopenjdk.icedteaweb.http.ConnectionFactory;
import net.adoptopenjdk.icedteaweb.jdk89access.JarIndexAccess;
import net.adoptopenjdk.icedteaweb.jnlp.element.EntryPoint;
import net.adoptopenjdk.icedteaweb.jnlp.element.application.AppletDesc;
import net.adoptopenjdk.icedteaweb.jnlp.element.application.ApplicationDesc;
import net.adoptopenjdk.icedteaweb.jnlp.element.resource.ExtensionDesc;
import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc;
import net.adoptopenjdk.icedteaweb.jnlp.element.resource.ResourcesDesc;
import net.adoptopenjdk.icedteaweb.jnlp.element.security.SecurityDesc;
import net.adoptopenjdk.icedteaweb.jnlp.version.VersionString;
import net.adoptopenjdk.icedteaweb.logging.Logger;
import net.adoptopenjdk.icedteaweb.logging.LoggerFactory;
import net.adoptopenjdk.icedteaweb.manifest.ManifestAttributesChecker;
import net.adoptopenjdk.icedteaweb.manifest.ManifestAttributesReader;
import net.adoptopenjdk.icedteaweb.resources.IllegalResourceDescriptorException;
import net.adoptopenjdk.icedteaweb.resources.ResourceTracker;
import net.adoptopenjdk.icedteaweb.resources.UpdatePolicy;
import net.adoptopenjdk.icedteaweb.resources.cache.Cache;
import net.adoptopenjdk.icedteaweb.xmlparser.ParseException;
import net.sourceforge.jnlp.JNLPFile;
import net.sourceforge.jnlp.JNLPFileFactory;
import net.sourceforge.jnlp.JNLPMatcher;
import net.sourceforge.jnlp.JNLPMatcherException;
import net.sourceforge.jnlp.LaunchException;
import net.sourceforge.jnlp.NullJnlpFileException;
import net.sourceforge.jnlp.ParserSettings;
import net.sourceforge.jnlp.cache.CacheUtil;
import net.sourceforge.jnlp.cache.NativeLibraryStorage;
import net.sourceforge.jnlp.config.ConfigurationConstants;
import net.sourceforge.jnlp.runtime.ApplicationInstance;
import net.sourceforge.jnlp.runtime.CachedJarFileCallback;
import net.sourceforge.jnlp.runtime.JNLPRuntime;
import net.sourceforge.jnlp.security.AppVerifier;
import net.sourceforge.jnlp.security.JNLPAppVerifier;
import net.sourceforge.jnlp.tools.JarCertVerifier;
import net.sourceforge.jnlp.util.JarFile;
import net.sourceforge.jnlp.util.UrlUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilePermission;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.SocketPermission;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessControlContext;
import java.security.AccessControlException;
import java.security.AccessController;
import java.security.AllPermission;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.stream.Stream;
import static net.adoptopenjdk.icedteaweb.i18n.Translator.R;
import static net.sourceforge.jnlp.LaunchException.FATAL;
import static net.sourceforge.jnlp.cache.NativeLibraryStorage.NATIVE_LIB_EXT_DYLIB;
import static net.sourceforge.jnlp.cache.NativeLibraryStorage.NATIVE_LIB_EXT_JNILIB;
import static net.sourceforge.jnlp.util.UrlUtils.FILE_PROTOCOL;
import static sun.security.util.SecurityConstants.FILE_READ_ACTION;
/**
* Classloader that takes it's resources from a JNLP file. If the JNLP file
* defines extensions, separate classloaders for these will be created
* automatically. Classes are loaded with the security context when the
* classloader was created.
*
* @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell
* (JAM)</a> - initial author
* @version $Revision: 1.20 $
*/
public class JNLPClassLoader extends URLClassLoader {
private static final Logger LOG = LoggerFactory.getLogger(JNLPClassLoader.class);
// todo: initializePermissions should get the permissions from
// extension classes too so that main file classes can load
// resources in an extension.
/**
* Signed JNLP File and Template
*/
private static final String TEMPLATE = "JNLP-INF/APPLICATION_TEMPLATE.JNLP";
private static final String APPLICATION = "JNLP-INF/APPLICATION.JNLP";
/**
* Actions to specify how cache is to be managed *
*/
enum DownloadAction {
DOWNLOAD_TO_CACHE, REMOVE_FROM_CACHE, CHECK_CACHE
}
public enum SigningState {
FULL, PARTIAL, NONE
}
/**
* True if the application has a signed JNLP File
*/
private boolean isSignedJNLP = false;
/**
* map from JNLPFile unique key to shared classloader
*/
private static final Map<String, JNLPClassLoader> uniqueKeyToLoader = new ConcurrentHashMap<>();
/**
* map from JNLPFile unique key to lock, the lock is needed to enforce
* correct initialization of applets that share a unique key
*/
private static final Map<String, ReentrantLock> uniqueKeyToLock = new HashMap<>();
/**
* Provides a search path & temporary storage for native code
*/
private final NativeLibraryStorage nativeLibraryStorage;
/**
* security context
*/
private final AccessControlContext acc = AccessController.getContext();
/**
* the permissions for the cached jar files
*/
private final List<Permission> resourcePermissions;
/**
* the app
*/
private ApplicationInstance app = null; // here for faster lookup in security manager
/**
* list of this, local and global loaders this loader uses
*/
private JNLPClassLoader[] loaders = null; // ..[0]==this
/**
* whether to strictly adhere to the spec or not
*/
private final boolean strict;
/**
* loads the resources
*/
private final ResourceTracker tracker;
/**
* the update policy for resources
*/
private final UpdatePolicy updatePolicy;
/**
* the JNLP file
*/
private final JNLPFile file;
/**
* the resources section
*/
private final ResourcesDesc resources;
/**
* the security section
*/
private SecurityDesc security;
/**
* Permissions granted by the user during runtime.
*/
private final ArrayList<Permission> runtimePermissions = new ArrayList<>();
/**
* all jars not yet part of classloader or active Synchronized since this
* field may become shared data between multiple classloading threads. See
* loadClass(String) and CodebaseClassLoader.findClassNonRecursive(String).
*/
private final List<JARDesc> available = Collections.synchronizedList(new ArrayList<>());
/**
* the jar cert verifier tool to verify our jars
*/
final JarCertVerifier jcv;
private SigningState signing = SigningState.NONE;
/**
* ArrayList containing jar indexes for various jars available to this
* classloader Synchronized since this field may become shared data between
* multiple classloading threads/ See loadClass(String) and
* CodebaseClassLoader.findClassNonRecursive(String).
*/
private final List<JarIndexAccess> jarIndexes = Collections.synchronizedList(new ArrayList<>());
/**
* Set of classpath strings declared in the manifest.mf files Synchronized
* since this field may become shared data between multiple classloading
* threads. See loadClass(String) and
* CodebaseClassLoader.findClassNonRecursive(String).
*/
private final Set<String> classpaths = Collections.synchronizedSet(new HashSet<>());
/**
* File entries in the jar files available to this classloader Synchronized
* sinc this field may become shared data between multiple classloading
* threads. See loadClass(String) and
* CodebaseClassLoader.findClassNonRecursive(String).
*/
private final Set<String> jarEntries = Collections.synchronizedSet(new TreeSet<>());
/**
* Map of specific original (remote) CodeSource Urls to securitydesc
* Synchronized since this field may become shared data between multiple
* classloading threads. See loadClass(String) and
* CodebaseClassLoader.findClassNonRecursive(String).
*/
final Map<URL, SecurityDesc> jarLocationSecurityMap = Collections.synchronizedMap(new HashMap<>());
/*Set to prevent once tried-to-get resources to be tried again*/
private final Set<URL> alreadyTried = Collections.synchronizedSet(new HashSet<>());
/**
* Loader for codebase (which is a path, rather than a file)
*/
private CodeBaseClassLoader codeBaseLoader;
/**
* True if the jar with the main class has been found
*/
private boolean foundMainJar = false;
/**
* Name of the application's main class
*/
private String mainClass;
/**
* Variable to track how many times this loader is in use
*/
private int useCount = 0;
private boolean enableCodeBase;
private final SecurityDelegate securityDelegate;
private ManifestAttributesChecker mac;
/**
* Create a new JNLPClassLoader from the specified file.
*
* @param file the JNLP file
* @param policy update policy of loader
* @throws net.sourceforge.jnlp.LaunchException if app can not be loaded
*/
public JNLPClassLoader(JNLPFile file, UpdatePolicy policy) throws LaunchException {
this(file, policy, null, false);
}
/**
* Create a new JNLPClassLoader from the specified file.
*
* @param file the JNLP file
* @param policy the UpdatePolicy for this class loader
* @param mainName name of the application's main class
* @param enableCodeBase switch whether this classloader can search in
* codebase or not
* @throws net.sourceforge.jnlp.LaunchException when need to kill an app
* comes.
*/
private JNLPClassLoader(JNLPFile file, UpdatePolicy policy, String mainName, boolean enableCodeBase) throws LaunchException {
super(new URL[0], JNLPClassLoader.class.getClassLoader());
LOG.info("New classloader: {}", file.getFileLocation());
strict = Boolean.parseBoolean(JNLPRuntime.getConfiguration().getProperty(ConfigurationConstants.KEY_STRICT_JNLP_CLASSLOADER));
this.file = file;
this.tracker = new ResourceTracker(true, file.getDownloadOptions(), JNLPRuntime.getDefaultUpdatePolicy());
this.updatePolicy = policy;
this.resources = file.getResources();
this.nativeLibraryStorage = new NativeLibraryStorage(tracker);
this.mainClass = mainName;
this.enableCodeBase = enableCodeBase;
final AppVerifier verifier = new JNLPAppVerifier();
jcv = new JarCertVerifier(verifier);
if (this.enableCodeBase) {
addToCodeBaseLoader(this.file.getCodeBase());
}
this.securityDelegate = new SecurityDelegateImpl(this);
if (mainClass == null) {
final EntryPoint entryPoint = file.getEntryPointDesc();
if (entryPoint instanceof ApplicationDesc || entryPoint instanceof AppletDesc) {
mainClass = entryPoint.getMainClass();
}
}
resourcePermissions = new ArrayList<>();
// initialize extensions
initializeExtensions();
initializeResources();
// initialize permissions
initializeReadJarPermissions();
installShutdownHooks();
}
private static boolean isCertUnderestimated() {
return Boolean.parseBoolean(JNLPRuntime.getConfiguration().getProperty(ConfigurationConstants.KEY_SECURITY_ITW_IGNORECERTISSUES))
&& !JNLPRuntime.isSecurityEnabled();
}
static void consultCertificateSecurityException(LaunchException ex) throws LaunchException {
if (isCertUnderestimated()) {
LOG.error("{} and {} are declared. Ignoring certificate issue", CommandLineOptions.NOSEC.getOption(), ConfigurationConstants.KEY_SECURITY_ITW_IGNORECERTISSUES);
} else {
throw ex;
}
}
/**
* Install JVM shutdown hooks to clean up resources allocated by this
* ClassLoader.
*/
private void installShutdownHooks() {
/*
* Delete only the native dir created by this classloader (if
* there is one). Other classloaders (parent, peers) will all
* cleanup things they created
*/
Runtime.getRuntime().addShutdownHook(new Thread(() -> nativeLibraryStorage.cleanupTemporaryFolder()));
}
private void setSecurity() throws LaunchException {
URL codebase = UrlUtils.guessCodeBase(file);
this.security = securityDelegate.getClassLoaderSecurity(codebase);
}
/**
* Gets the lock for a given unique key, creating one if it does not yet
* exist. This operation is atomic & thread-safe.
*
* @param uniqueKey the file whose unique key should be used
* @return the lock
*/
private static ReentrantLock getUniqueKeyLock(String uniqueKey) {
synchronized (uniqueKeyToLock) {
ReentrantLock storedLock = uniqueKeyToLock.get(uniqueKey);
if (storedLock == null) {
storedLock = new ReentrantLock();
uniqueKeyToLock.put(uniqueKey, storedLock);
}
return storedLock;
}
}
/**
* Creates a fully initialized JNLP classloader for the specified JNLPFile,
* to be used as an applet/application's classloader. In contrast, JNLP
* classloaders can also be constructed simply to merge its resources into
* another classloader.
*
* @param file the file to load classes for
* @param policy the update policy to use when downloading resources
* @param mainName Overrides the main class name of the application
*/
private static JNLPClassLoader createInstance(JNLPFile file, UpdatePolicy policy, String mainName, boolean enableCodeBase) throws LaunchException {
String uniqueKey = file.getUniqueKey();
JNLPClassLoader baseLoader = uniqueKeyToLoader.get(uniqueKey);
JNLPClassLoader loader = new JNLPClassLoader(file, policy, mainName, enableCodeBase);
// If security level is 'high' or greater, we must check if the user allows unsigned applets
// when the JNLPClassLoader is created. We do so here, because doing so in the constructor
// causes unwanted side-effects for some applets. However, if the loader has been tagged
// with "runInSandbox", then we do not show this dialog - since this tag indicates that
// the user was already shown a CertWarning dialog and has chosen to run the applet sandboxed.
// This means they've already agreed to running the applet and have specified with which
// permission level to do it!
if (loader.getSigningState() == SigningState.PARTIAL) {
loader.securityDelegate.promptUserOnPartialSigning();
}
// New loader init may have caused extensions to create a
// loader for this unique key. Check.
JNLPClassLoader extLoader = uniqueKeyToLoader.get(uniqueKey);
if (extLoader != null && extLoader != loader) {
if (loader.getSigning() != extLoader.getSigning()) {
loader.securityDelegate.promptUserOnPartialSigning();
}
loader.merge(extLoader);
extLoader.decrementLoaderUseCount(); // loader urls have been merged, ext loader is no longer used
}
// loader is now current + ext. But we also need to think of
// the baseLoader
if (baseLoader != null && baseLoader != loader) {
loader.merge(baseLoader);
}
return loader;
}
/**
* Returns a JNLP classloader for the specified JNLP file.
*
* @param file the file to load classes for
* @param policy the update policy to use when downloading resources
* @param enableCodeBase true if codebase can be searched (ok for
* applets,false for apps)
* @return existing classloader. creates new if none reliable exists
* @throws net.sourceforge.jnlp.LaunchException when launch is doomed
*/
public static JNLPClassLoader getInstance(JNLPFile file, UpdatePolicy policy, boolean enableCodeBase) throws LaunchException {
return getInstance(file, policy, null, enableCodeBase);
}
/**
* Returns a JNLP classloader for the specified JNLP file.
*
* @param file the file to load classes for
* @param policy the update policy to use when downloading resources
* @param mainName Overrides the main class name of the application
* @param enableCodeBase ue if codebase can be searched (ok for
* applets,false for apps)
* @return existing classloader. creates new if none reliable exists
* @throws net.sourceforge.jnlp.LaunchException when launch is doomed
*/
private static JNLPClassLoader getInstance(JNLPFile file, UpdatePolicy policy, String mainName, boolean enableCodeBase) throws LaunchException {
JNLPClassLoader loader;
String uniqueKey = file.getUniqueKey();
synchronized (getUniqueKeyLock(uniqueKey)) {
JNLPClassLoader baseLoader = uniqueKeyToLoader.get(uniqueKey);
// A null baseloader implies that no loader has been created
// for this codebase/jnlp yet. Create one.
if (baseLoader == null
|| (file.isApplication()
&& (file.getFileLocation() == null || !baseLoader.getJNLPFile().getFileLocation().toString().equals(file.getFileLocation().toString())))) {
loader = createInstance(file, policy, mainName, enableCodeBase);
} else {
// if key is same and locations match, this is the loader we want
if (!file.isApplication()) {
// If this is an applet, we do need to consider its loader
loader = new JNLPClassLoader(file, policy, mainName, enableCodeBase);
baseLoader.merge(loader);
}
loader = baseLoader;
}
// loaders are mapped to a unique key. Only extensions and parent
// share a key, so it is safe to always share based on it
loader.incrementLoaderUseCount();
uniqueKeyToLoader.put(uniqueKey, loader);
}
return loader;
}
/**
* Returns a JNLP classloader for the JNLP file at the specified location.
*
* @param location the file's location
* @param uniqueKey key to manage applets/applications in shared vm
* @param version the file's version
* @param settings settings of parser
* @param policy the update policy to use when downloading resources
* @param mainName Overrides the main class name of the application
* @param enableCodeBase whether to enable codebase search or not
* @return classloader of this app
* @throws java.io.IOException when IO fails
* @throws ParseException when parsing fails
* @throws net.sourceforge.jnlp.LaunchException when launch is doomed
*/
private static JNLPClassLoader getInstance(final URL location, final String uniqueKey, final VersionString version, final ParserSettings settings, final UpdatePolicy policy, final String mainName, boolean enableCodeBase)
throws IOException, ParseException, LaunchException {
JNLPClassLoader loader;
synchronized (getUniqueKeyLock(uniqueKey)) {
loader = uniqueKeyToLoader.get(uniqueKey);
if (loader == null || loader.getJNLPFile().getFileLocation() == null || !location.toString().equals(loader.getJNLPFile().getFileLocation().toString())) {
final JNLPFile jnlpFile = new JNLPFileFactory().create(location, uniqueKey, version, settings, policy);
loader = getInstance(jnlpFile, policy, mainName, enableCodeBase);
}
}
return loader;
}
/**
* Load the extensions specified in the JNLP file.
*/
private void initializeExtensions() {
LOG.debug("initializeExtensions");
final List<Exception> exceptions = new ArrayList<>();
final List<JNLPClassLoader> loaderList = new ArrayList<>();
loaderList.add(this);
final ExtensionDesc[] extDescs = resources.getExtensions();
if (extDescs != null) {
final String uniqueKey = this.getJNLPFile().getUniqueKey();
for (ExtensionDesc ext : extDescs) {
try {
final JNLPClassLoader loader = getInstance(ext.getLocation(), uniqueKey, ext.getVersion(), file.getParserSettings(), updatePolicy, mainClass, enableCodeBase);
loaderList.add(loader);
} catch (Exception ex) {
exceptions.add(new Exception("Exception while initializing extension '" + ext.getLocation() + "'", ex));
}
}
}
if (exceptions.size() > 0) {
exceptions.forEach(e -> LOG.error(e.getMessage(), e.getCause()));
throw new RuntimeException(exceptions.get(0));
}
loaders = loaderList.toArray(new JNLPClassLoader[0]);
}
/**
* Make permission objects for the classpath.
*/
private void initializeReadJarPermissions() {
JARDesc[] jars = resources.getJARs();
int counter = 0;
for (JARDesc jar : jars) {
Permission p = getReadPermission(jar);
if (p == null) {
LOG.info("Unable to add permission for {}", jar.getLocation());
} else {
resourcePermissions.add(p);
LOG.debug("Permission added: {}", p.toString());
counter++;
}
}
LOG.info("Added permissions for {} jars", counter);
}
private Permission getReadPermission(JARDesc jar) {
final URL location = jar.getLocation();
if (CacheUtil.isCacheable(location)) {
final File cacheFile = tracker.getCacheFile(location);
if (cacheFile != null) {
return new FilePermission(cacheFile.getPath(), FILE_READ_ACTION);
} else {
LOG.debug("No cache file for cacheable resource '{}' found.", location);
return null;
}
} else {
// this is what URLClassLoader does
try (final CloseableConnection conn = ConnectionFactory.openConnection(location)) {
return conn.getPermission();
} catch (IOException ioe) {
LOG.error("Exception while retrieving permissions from connection to " + location, ioe);
}
}
// should try to figure out the permission
return null;
}
/**
* Check if a described jar file is invalid
*
* @param jar the jar to check
* @return true if file exists AND is an invalid jar, false otherwise
*/
boolean isInvalidJar(JARDesc jar) {
File cacheFile = tracker.getCacheFile(jar.getLocation());
if (cacheFile == null) {
return false;//File cannot be retrieved, do not claim it is an invalid jar
}
boolean isInvalid = false;
try {
JarFile jarFile = new JarFile(cacheFile.getAbsolutePath());
jarFile.close();
} catch (IOException ioe) {
//Catch a ZipException or any other read failure
isInvalid = true;
}
return isInvalid;
}
/**
* Load all of the JARs used in this JNLP file into the ResourceTracker for
* downloading.
*/
private void initializeResources() throws LaunchException {
final JARDesc[] jars = resources.getJARs();
if (jars.length == 0) {
LOG.debug("no jars defined in jnlp file '{}'", file.getSourceLocation());
if (loaders.length > 1) {
LOG.debug("Checking extensions of jnlp file '{}'", file.getSourceLocation());
// skip the first loader as it is the jnlp that points to extensions and that it has no jars and hence signing should not be checked
final boolean containsUnsigned = Stream.of(loaders).skip(1).anyMatch(l -> !l.getSigning());
if (containsUnsigned) {
LOG.debug("At least one extension for jnlp file '{}' contains unsigned content", file.getSourceLocation());
//TODO: is NONE really right? We do not kn ow if it is NONE or PARTIAL....
signing = SigningState.NONE;
} else {
LOG.debug("All extensions of jnlp file '{}' are fully signed", file.getSourceLocation());
signing = SigningState.FULL;
}
} else {
LOG.debug("JNLP file {} does not contain any jars or extensions and therefore is marked as fully signed", file.getSourceLocation());
signing = SigningState.FULL;
}
//Check if main jar is found within extensions
foundMainJar = foundMainJar || hasMainInExtensions();
setSecurity();
initializeManifestAttributesChecker();
mac.checkAll();
return;
}
final List<JARDesc> initialJars = new ArrayList<>();
for (JARDesc jar : jars) {
available.add(jar);
if (jar.isEager() || jar.isMain()) {
initialJars.add(jar); // regardless of part
}
// FIXME: this will trigger an eager download as the tracker is created with prefetch == true
tracker.addResource(jar.getLocation(), jar.getVersion(),
jar.isCacheable() ? JNLPRuntime.getDefaultUpdatePolicy() : UpdatePolicy.FORCE);
}
//If there are no eager jars, initialize the first jar
if (initialJars.isEmpty()) {
initialJars.add(jars[0]);
}
if (strict) {
fillInPartJars(initialJars); // add in each initial part's lazy jars
}
waitForJars(initialJars); //download the jars first.
if (JNLPRuntime.isVerifying()) {
try {
jcv.add(initialJars, tracker);
} catch (Exception e) {
//we caught an Exception from the JarCertVerifier class.
//Note: one of these exceptions could be from not being able
//to read the cacerts or trusted.certs files.
LOG.error("Exception while verifying jars", e);
LaunchException ex = new LaunchException(file, e, FATAL,
"Initialization Error", "A fatal error occurred while trying to verify jars.", "An exception has been thrown in class JarCertVerifier. Being unable to read the cacerts or trusted.certs files could be a possible cause for this exception.: " + e.getMessage());
consultCertificateSecurityException(ex);
}
//Case when at least one jar has some signing
if (jcv.isFullySigned()) {
signing = SigningState.FULL;
// Check for main class in the downloaded jars, and check/verify signed JNLP fill
checkForMain(initialJars);
// If jar with main class was not found, check available resources
while (!foundMainJar && !available.isEmpty()) {
addNextResource();
}
// If the jar with main class was not found, check extension
// jnlp's resources
foundMainJar = foundMainJar || hasMainInExtensions();
boolean externalAppletMainClass = file.getEntryPointDesc() != null && !foundMainJar && available.isEmpty();
// We do this check here simply to ensure that if there are no JARs at all,
// and also no main-class in the codebase (ie the applet doesn't really exist), we
// fail ASAP rather than continuing (and showing the NotAllSigned dialog for no applet)
if (externalAppletMainClass) {
if (codeBaseLoader != null) {
try {
codeBaseLoader.findClass(mainClass);
} catch (ClassNotFoundException extCnfe) {
LOG.error("Could not determine the main class for this application.", extCnfe);
throw new LaunchException(file, extCnfe, FATAL, "Initialization Error", "Unknown Main-Class.", "Could not determine the main class for this application.");
}
} else {
throw new LaunchException(file, null, FATAL, "Initialization Error", "Unknown Main-Class.", "Could not determine the main class for this application.");
}
}
// If externalAppletMainClass is true and a LaunchException was not thrown above,
// then the main-class can be loaded from the applet codebase, but is obviously not signed
if (externalAppletMainClass) {
checkPartialSigningWithUser();
}
// If main jar was found, but a signed JNLP file was not located
if (!isSignedJNLP && foundMainJar) {
file.setSignedJNLPAsMissing();
}
//user does not trust this publisher
if (!jcv.isTriviallySigned()) {
checkTrustWithUser();
}
} else {
// Otherwise this jar is simply unsigned -- make sure to ask
// for permission on certain actions
signing = SigningState.NONE;
}
}
setSecurity();
final Set<JARDesc> validJars = new HashSet<>();
boolean containsSignedJar = false, containsUnsignedJar = false;
for (JARDesc jarDesc : file.getResources().getJARs()) {
File cachedFile;
try {
cachedFile = tracker.getCacheFile(jarDesc.getLocation());
} catch (IllegalResourceDescriptorException irde) {
//Caused by ignored resource being removed due to not being valid
LOG.error("JAR " + jarDesc.getLocation() + " is not a valid jar file. Continuing.", irde);
continue;
}
if (cachedFile == null) {
LOG.warn("initializeResource JAR {} not found. Continuing.", jarDesc.getLocation());
continue; // JAR not found. Keep going.
}
validJars.add(jarDesc);
final URL codebase = getJnlpFileCodebase();
final SecurityDesc jarSecurity = securityDelegate.getCodebaseSecurityDesc(jarDesc, codebase);
if (jarSecurity.getSecurityType().equals(SecurityDesc.SANDBOX_PERMISSIONS)) {
containsUnsignedJar = true;
} else {
containsSignedJar = true;
}
if (containsUnsignedJar && containsSignedJar) {
signing = SigningState.PARTIAL;
break;
}
}
if (containsSignedJar && containsUnsignedJar) {
checkPartialSigningWithUser();
}
setSecurity();
initializeManifestAttributesChecker();
mac.checkAll();
for (JARDesc jarDesc : validJars) {
final URL codebase = getJnlpFileCodebase();
final SecurityDesc jarSecurity = securityDelegate.getCodebaseSecurityDesc(jarDesc, codebase);
jarLocationSecurityMap.put(jarDesc.getLocation(), jarSecurity);
}
activateJars(initialJars);
}
private void initializeManifestAttributesChecker() {
if (mac == null) {
file.getManifestAttributesReader().setLoader(this);
mac = new ManifestAttributesChecker(security, file, signing, securityDelegate);
}
}
private URL getJnlpFileCodebase() {
final URL codebase;
if (file.getCodeBase() != null) {
codebase = file.getCodeBase();
} else {
// FIXME: codebase should be the codebase of the Main Jar not
// the location. Although, it still works in the current state.
codebase = file.getResources().getMainJAR().getLocation();
}
return codebase;
}
/**
* *
* Checks for the jar that contains the main class. If the main class was
* found, it checks to see if the jar is signed and whether it contains a
* signed JNLP file
*
* @param jars Jars that are checked to see if they contain the main class
* @throws LaunchException Thrown if the signed JNLP file, within the main
* jar, fails to be verified or does not match
*/
void checkForMain(List<JARDesc> jars) throws LaunchException {
// Check launch info
if (mainClass == null) {
final EntryPoint entryPoint = file.getEntryPointDesc();
if (entryPoint != null) {
mainClass = entryPoint.getMainClass();
}
}
// The main class may be specified in the manifest
if (mainClass == null) {
mainClass = ManifestAttributesReader.getAttributeFromJars(Attributes.Name.MAIN_CLASS, jars, tracker);
}
final String desiredJarEntryName = mainClass + ".class";
for (JARDesc jar : jars) {
try {
final File localFile = tracker.getCacheFile(jar.getLocation());
if (localFile == null) {
LOG.warn("checkForMain JAR {} not found. Continuing.", jar.getLocation());
continue; // JAR not found. Keep going.
}
final JarFile jarFile = new JarFile(localFile);
for (JarEntry entry : Collections.list(jarFile.entries())) {
String jeName = entry.getName().replaceAll("/", ".");
if (jeName.equals(desiredJarEntryName)) {
foundMainJar = true;
verifySignedJNLP(jarFile);
break;
}
}
jarFile.close();
} catch (IOException e) {
/*
* After this exception is caught, it is escaped. This will skip
* the jarFile that may have thrown this exception and move on
* to the next jarFile (if there are any)
*/
}
}
}
/**
* @return true if this loader has the main jar
*/
boolean hasMainJar() {
return this.foundMainJar;
}
/**
* Returns true if extension loaders have the main jar
*/
private boolean hasMainInExtensions() {
boolean foundMain = false;
for (int i = 1; i < loaders.length && !foundMain; i++) {
foundMain = loaders[i].hasMainJar();
}
return foundMain;
}
/**
* Is called by checkForMain() to check if the jar file is signed and if it
* contains a signed JNLP file.
*
* @param jarFile the jar file
* @throws LaunchException thrown if the signed JNLP file, within the main
* jar, fails to be verified or does not match
*/
private void verifySignedJNLP(JarFile jarFile) throws LaunchException {
try {
// NOTE: verification should have happened by now. In other words,
// calling jcv.verifyJars(desc, tracker) here should have no affect.
if (jcv.isFullySigned()) {
for (JarEntry je : Collections.list(jarFile.entries())) {
String jeName = je.getName().toUpperCase();
if (jeName.equals(TEMPLATE) || jeName.equals(APPLICATION)) {
LOG.debug("Creating Jar InputStream from JarEntry");
InputStream inStream = jarFile.getInputStream(je);
LOG.debug("Creating File InputStream from launching JNLP file");
JNLPFile jnlp = this.getJNLPFile();
File jn;
// If the file is on the local file system, use original path, otherwise find cached file
if (jnlp.getFileLocation().getProtocol().toLowerCase().equals(FILE_PROTOCOL)) {
jn = new File(jnlp.getFileLocation().getPath());
} else {
jn = Cache.getCacheFile(jnlp.getFileLocation(), jnlp.getFileVersion());
}
InputStream jnlpStream = new FileInputStream(jn);
JNLPMatcher matcher;
if (jeName.equals(APPLICATION)) { // If signed application was found
LOG.debug("APPLICATION.JNLP has been located within signed JAR. Starting verification...");
matcher = new JNLPMatcher(inStream, jnlpStream, false, jnlp.getParserSettings());
} else { // Otherwise template was found
LOG.debug("APPLICATION_TEMPLATE.JNLP has been located within signed JAR. Starting verification...");
matcher = new JNLPMatcher(inStream, jnlpStream, true, jnlp.getParserSettings());
}
// If signed JNLP file does not matches launching JNLP file, throw JNLPMatcherException
if (!matcher.isMatch()) {
throw new JNLPMatcherException("Signed Application did not match launching JNLP File");
}
this.isSignedJNLP = true;
LOG.debug("Signed Application Verification Successful");
break;
}
}
}
} catch (JNLPMatcherException e) {
/*
* Throws LaunchException if signed JNLP file fails to be verified
* or fails to match the launching JNLP file
*/
LaunchException ex = new LaunchException(file, null, FATAL, "Application Error",
"The signed JNLP file did not match the launching JNLP file.", R(e.getMessage()));
consultCertificateSecurityException(ex);
/*
* Throwing this exception will fail to initialize the application
* resulting in the termination of the application
*/
} catch (Exception e) {
LOG.error("failed to validate the JNLP file itself", e);
/*
* After this exception is caught, it is escaped. If an exception is
* thrown while handling the jar file, (mainly for
* JarCertVerifier.add) it assumes the jar file is unsigned and