Merge branch 'improve_updatecenter_wip'

Review on private@netbeans.apache.org - References:

https://lists.apache.org/thread.html/3313fe7ecab7d861356f2a2d218041ee97aafec588120ade4e7480d3%40%3Cprivate.netbeans.apache.org%3E
https://lists.apache.org/thread.html/3313fe7ecab7d861356f2a2d218041ee97aafec588120ade4e7480d3%40%3Cprivate.netbeans.apache.org%3E
https://lists.apache.org/thread.html/4b628153ef6ce965d757608926e2a6dae1ebee3a6ba9ee630c4d7c6d%40%3Cprivate.netbeans.apache.org%3E
diff --git a/nb/updatecenters/src/org/netbeans/modules/updatecenters/resources/mf-layer.xml b/nb/updatecenters/src/org/netbeans/modules/updatecenters/resources/mf-layer.xml
index de190a3..b57d7a8 100644
--- a/nb/updatecenters/src/org/netbeans/modules/updatecenters/resources/mf-layer.xml
+++ b/nb/updatecenters/src/org/netbeans/modules/updatecenters/resources/mf-layer.xml
@@ -31,6 +31,7 @@
           <attr name="url" bundlevalue="org.netbeans.modules.updatecenters.resources.Bundle#URL_Distribution"/>
           <attr name="category" stringvalue="STANDARD"/>
           <attr name="enabled" boolvalue="true"/>
+          <attr name="trusted" boolvalue="true"/>
           <attr name="instanceOf" stringvalue="org.netbeans.spi.autoupdate.UpdateProvider" />
           <attr name="instanceCreate" methodvalue="org.netbeans.modules.autoupdate.updateprovider.AutoupdateCatalogFactory.createUpdateProvider"/>
       </file>
diff --git a/nbbuild/antsrc/org/netbeans/nbbuild/MakeUpdateDesc.java b/nbbuild/antsrc/org/netbeans/nbbuild/MakeUpdateDesc.java
index 825ffba..90a30e0 100644
--- a/nbbuild/antsrc/org/netbeans/nbbuild/MakeUpdateDesc.java
+++ b/nbbuild/antsrc/org/netbeans/nbbuild/MakeUpdateDesc.java
@@ -28,6 +28,7 @@
 import org.apache.tools.ant.DirectoryScanner;
 import java.io.File;
 import java.io.FileFilter;
+import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -36,17 +37,22 @@
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.nio.file.Files;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.text.Collator;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map.Entry;
 import java.util.Properties;
 import java.util.StringTokenizer;
 import java.util.TimeZone;
@@ -55,8 +61,11 @@
 import java.util.jar.Attributes;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
 import org.apache.tools.ant.Project;
 import org.apache.tools.ant.types.Path;
@@ -105,6 +114,19 @@
     private List<Group> groups = new ArrayList<>();
     private List<FileSet> filesets = new ArrayList<>();
 
+    private List<String> includeMessageDigests = Arrays.asList("SHA-512");
+
+    public String getIncludeMessageDigests() {
+        return includeMessageDigests.stream().collect(Collectors.joining(" "));
+    }
+
+    public void setIncludeMessageDigests(String includeMessageDigests) {
+        if(includeMessageDigests == null) {
+            includeMessageDigests = "";
+        }
+        this.includeMessageDigests = Arrays.asList(includeMessageDigests.split(" "));
+    }
+
     private File desc;
 
     /** Description file to create. */
@@ -301,7 +323,9 @@
                     }
                     File desc_ent = new File(ent_name);
                     desc_ent.delete();
-                    if (isPreferredUpdateDefined || (contentDescription != null && ! contentDescription.isEmpty())) {
+                    if (includeMessageDigests != null && (! includeMessageDigests.isEmpty())) {
+                        pw.println("<!DOCTYPE module_updates PUBLIC \"-//NetBeans//DTD Autoupdate Catalog 2.8//EN\" \"http://www.netbeans.org/dtds/autoupdate-catalog-2_8.dtd\" [");
+                    } else if (isPreferredUpdateDefined || (contentDescription != null && ! contentDescription.isEmpty())) {
                         pw.println("<!DOCTYPE module_updates PUBLIC \"-//NetBeans//DTD Autoupdate Catalog 2.7//EN\" \"http://www.netbeans.org/dtds/autoupdate-catalog-2_7.dtd\" [");
                     } else if (useLicenseUrl) {
                         pw.println("<!DOCTYPE module_updates PUBLIC \"-//NetBeans//DTD Autoupdate Catalog 2.6//EN\" \"http://www.netbeans.org/dtds/autoupdate-catalog-2_6.dtd\" [");
@@ -339,7 +363,9 @@
                     pw.println ();
                     
                 } else {
-                    if (isPreferredUpdateDefined || (contentDescription != null && ! contentDescription.isEmpty())) {
+                    if (includeMessageDigests != null && (! includeMessageDigests.isEmpty())) {
+                        pw.println("<!DOCTYPE module_updates PUBLIC \"-//NetBeans//DTD Autoupdate Catalog 2.8//EN\" \"http://www.netbeans.org/dtds/autoupdate-catalog-2_8.dtd\">");
+                    } else if (isPreferredUpdateDefined || (contentDescription != null && ! contentDescription.isEmpty())) {
                         pw.println("<!DOCTYPE module_updates PUBLIC \"-//NetBeans//DTD Autoupdate Catalog 2.7//EN\" \"http://www.netbeans.org/dtds/autoupdate-catalog-2_7.dtd\">");
                     } else if (useLicenseUrl) {
                         pw.println("<!DOCTYPE module_updates PUBLIC \"-//NetBeans//DTD Autoupdate Catalog 2.6//EN\" \"http://www.netbeans.org/dtds/autoupdate-catalog-2_6.dtd\">");
@@ -642,6 +668,15 @@
                                     }
                                 }
                             }
+
+                            Map<String,String> messageDigests = createMessageDigests(n_file);
+                            for(Entry<String,String> messageDigest: messageDigests.entrySet()) {
+                                Element messageDigestElement = m.xml.getOwnerDocument().createElement("message_digest");
+                                messageDigestElement.setAttribute("algorithm", messageDigest.getKey());
+                                messageDigestElement.setAttribute("value", messageDigest.getValue());
+                                m.xml.appendChild(messageDigestElement);
+                            }
+
                             moduleCollection.add(m);
                         }
                     } catch (BuildException x) {
@@ -882,4 +917,57 @@
         return sb;
     }
 
+    private Map<String,String> createMessageDigests(File targetFile) {
+        if(includeMessageDigests.isEmpty()) {
+            return Collections.<String,String>emptyMap();
+        }
+
+        Map<String,MessageDigest> digests = new HashMap<>(includeMessageDigests.size());
+
+        for(String messageDigest: includeMessageDigests) {
+            try {
+                digests.put(messageDigest, MessageDigest.getInstance(messageDigest));
+            } catch (NoSuchAlgorithmException ex) {
+                throw new BuildException("Failed to load requested messageDigest: " + messageDigest, ex);
+            }
+        }
+
+        try(FileInputStream fis = new FileInputStream(targetFile)) {
+            byte[] buffer = new byte[102400];
+            int read;
+            while((read = fis.read(buffer)) >= 0) {
+                for(MessageDigest md: digests.values()) {
+                    md.update(buffer, 0, read);
+                }
+            }
+        } catch (IOException ex) {
+            Logger.getLogger(MakeUpdateDesc.class.getName()).log(Level.SEVERE, null, ex);
+        }
+
+        Map<String,String> result = new HashMap<>();
+        for(Entry<String,MessageDigest> md: digests.entrySet()) {
+            result.put(md.getKey(), hexEncode(md.getValue().digest()));
+        }
+        return result;
+    }
+
+    private static String hexEncode(byte[] input) {
+        StringBuilder sb = new StringBuilder(input.length * 2);
+        for(byte b: input) {
+            sb.append(Character.forDigit((b & 0xF0) >> 4, 16));
+            sb.append(Character.forDigit((b & 0x0F), 16));
+        }
+        return sb.toString();
+    }
+
+    private static byte[] hexDecode(String input) {
+        int length = input.length() / 2;
+        byte[] result = new byte[length];
+        for(int i = 0; i < length; i++) {
+            int b = Character.digit(input.charAt(i * 2), 16) << 4;
+            b |= Character.digit(input.charAt(i * 2 + 1), 16);
+            result[i] = (byte) (b & 0xFF);
+        }
+        return result;
+    }
 }
diff --git a/platform/autoupdate.services/apichanges.xml b/platform/autoupdate.services/apichanges.xml
index 9cee0f7..a51099c 100644
--- a/platform/autoupdate.services/apichanges.xml
+++ b/platform/autoupdate.services/apichanges.xml
@@ -33,6 +33,47 @@
     <!-- ACTUAL CHANGES BEGIN HERE: -->
 
     <changes>
+        <change id="enable-trusted-update-centers">
+            <api name="general"/>
+            <summary>KeyStoreProviders can now report which trustlevel they intent to supply</summary>
+            <version major="1" minor="62"/>
+            <date day="4" month="11" year="2019"/>
+            <author login="matthiasblaesing"/>
+            <compatibility addition="yes" binary="compatible" deletion="no" deprecation="no" semantic="compatible" source="compatible"/>
+            <description>
+                <p>
+                    The UpdateUnitProvider can now be declared trusted. If
+                    UpdateItems are provided by a trusted UpdateUnitProvider
+                    and can be linked to it (for example because the provider provides
+                    checksums in the catalog), they are considered trusted by the Autoupdate
+                    mechanism and will not trigger certificate warnings.
+                </p>
+            </description>
+            <class package="org.netbeans.api.autoupdate" name="OperationContainer"/>
+        </change>
+        <change id="keystores-for-validation">
+            <api name="general"/>
+            <summary>KeyStoreProviders can now report which trustlevel they intent to supply</summary>
+            <version major="1" minor="61"/>
+            <date day="4" month="11" year="2019"/>
+            <author login="matthiasblaesing"/>
+            <compatibility addition="yes" binary="compatible" deletion="no" deprecation="no" semantic="compatible" source="compatible"/>
+            <description>
+                <p>
+                    The validation of signatures of NBMs was done by checking if at least a partial certificate chain was present
+                    and if that chain was valid (i.e. not expired, not revoked). Instead of relying on this partial check this
+                    version bases the verification on a list of trusted certificates, similar to the existing KeyStoreProvider.
+                </p>
+                <p>
+                    The existing KeyStoreProviders provide Certificates, that are fully trusted. The new getTrustLevel method
+                    allows a KeyStoreProvider to provide certificate for the new, lower trusted level. In addition to the to
+                    levels TRUST and VALIDATE, two variants: TRUST_CA and VALIDATE_CA are introduced. Certificates provided with
+                    that level are expected to be CA certificates and they are only trusted if a `CertPathValidator` validates
+                    the chain.
+                </p>
+            </description>
+            <class package="org.netbeans.api.autoupdate" name="OperationContainer"/>
+        </change>
         <change id="missing-elements">
             <api name="general"/>
             <summary>Report parts of a feature which is not installed yet</summary>
diff --git a/platform/autoupdate.services/build.xml b/platform/autoupdate.services/build.xml
index 8e0f1ed..33f8799 100644
--- a/platform/autoupdate.services/build.xml
+++ b/platform/autoupdate.services/build.xml
@@ -52,7 +52,22 @@
     <target name="netbeans-extra" depends="jar-updater"/>
 
     <target name="do-unit-test-build" depends="projectized.do-unit-test-build">
+        <property name="validation-store" value="${build.test.unit.classes.dir}/org/netbeans/api/autoupdate/data/test-validate-keystore.jks" />
+        <delete file="${validation-store}" />
+        <genkey
+            keystore="${validation-store}"
+            alias="demo-key" storepass="password"
+            dname="CN=Demo Key, OU=NetBeans, O=Apache.org, C=US"
+        />
+        <property name="unvalidation-store" value="${build.test.unit.classes.dir}/org/netbeans/api/autoupdate/data/test-unvalidate-keystore.jks" />
+        <delete file="${unvalidation-store}" />
+        <genkey
+            keystore="${unvalidation-store}"
+            alias="demo-key-unvalidated" storepass="password"
+            dname="CN=Demo Key (unvalidated), OU=NetBeans, O=Apache.org, C=US"
+        />
         <touch file="${build.dir}/Dummy.class" />
+        <touch file="${build.dir}/Dummy2.class" />
         <jar destfile="${build.test.unit.classes.dir}/org/netbeans/api/autoupdate/data/dummy-signed.jar">
             <zipfileset prefix="dummy/" file="${build.dir}/Dummy.class"/>
         </jar>
@@ -71,6 +86,31 @@
                  keystore="${test.unit.src.dir}/org/netbeans/api/autoupdate/data/test-keystore.jks"
                  storepass="password"
                  alias="test-two" />
+        <jar destfile="${build.test.unit.classes.dir}/org/netbeans/api/autoupdate/data/dummy-partial-signed.jar">
+            <zipfileset prefix="dummy/" file="${build.dir}/Dummy.class"/>
+        </jar>
+        <signjar jar="${build.test.unit.classes.dir}/org/netbeans/api/autoupdate/data/dummy-partial-signed.jar"
+                 keystore="${test.unit.src.dir}/org/netbeans/api/autoupdate/data/test-keystore.jks"
+                 storepass="password"
+                 alias="test-one" />
+        <zip destfile="${build.test.unit.classes.dir}/org/netbeans/api/autoupdate/data/dummy-partial-signed.jar"
+             update="true">
+            <zipfileset prefix="dummy/" file="${build.dir}/Dummy2.class" />
+        </zip>
+        <jar destfile="${build.test.unit.classes.dir}/org/netbeans/api/autoupdate/data/dummy-validated.jar">
+            <zipfileset prefix="dummy/" file="${build.dir}/Dummy.class"/>
+        </jar>
+        <signjar jar="${build.test.unit.classes.dir}/org/netbeans/api/autoupdate/data/dummy-validated.jar"
+                 keystore="${validation-store}"
+                 storepass="password"
+                 alias="demo-key" />
+        <jar destfile="${build.test.unit.classes.dir}/org/netbeans/api/autoupdate/data/dummy-unvalidated.jar">
+            <zipfileset prefix="dummy/" file="${build.dir}/Dummy.class"/>
+        </jar>
+        <signjar jar="${build.test.unit.classes.dir}/org/netbeans/api/autoupdate/data/dummy-unvalidated.jar"
+                 keystore="${unvalidation-store}"
+                 storepass="password"
+                 alias="demo-key-unvalidated" />
     </target>
 
 </project>
diff --git a/platform/autoupdate.services/libsrc/org/netbeans/updater/XMLUtil.java b/platform/autoupdate.services/libsrc/org/netbeans/updater/XMLUtil.java
index 7ce7088..b0e7560 100644
--- a/platform/autoupdate.services/libsrc/org/netbeans/updater/XMLUtil.java
+++ b/platform/autoupdate.services/libsrc/org/netbeans/updater/XMLUtil.java
@@ -183,6 +183,8 @@
                     return new InputSource(XMLUtil.class.getResource("resources/autoupdate-catalog-2_7.dtd").toString()); // NOI18N
                 } else if ("-//NetBeans//DTD Autoupdate Module Info 2.7//EN".equals(publicID)) { // NOI18N
                     return new InputSource(XMLUtil.class.getResource("resources/autoupdate-info-2_7.dtd").toString()); // NOI18N
+                } else if ("-//NetBeans//DTD Autoupdate Catalog 2.8//EN".equals(publicID)) { // NOI18N
+                    return new InputSource(XMLUtil.class.getResource("resources/autoupdate-catalog-2_8.dtd").toString()); // NOI18N
                 } else {
                     if (systemID.endsWith(".dtd")) { // NOI18N
                         return new InputSource(new ByteArrayInputStream(new byte[0]));
diff --git a/platform/autoupdate.services/libsrc/org/netbeans/updater/resources/autoupdate-catalog-2_8.dtd b/platform/autoupdate.services/libsrc/org/netbeans/updater/resources/autoupdate-catalog-2_8.dtd
new file mode 100644
index 0000000..074e636
--- /dev/null
+++ b/platform/autoupdate.services/libsrc/org/netbeans/updater/resources/autoupdate-catalog-2_8.dtd
@@ -0,0 +1,96 @@
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<!-- -//NetBeans//DTD Autoupdate Catalog 2.8//EN -->
+<!-- XML representation of Autoupdate Modules/Updates Catalog -->
+
+<!ELEMENT module_updates ((notification?, content_description?, (module_group|module)*, license*)|error)>
+<!ATTLIST module_updates timestamp CDATA #REQUIRED>
+
+<!ELEMENT module_group ((module_group|module)*)>
+<!ATTLIST module_group name CDATA #REQUIRED>
+
+<!ELEMENT notification (#PCDATA)>
+<!ATTLIST notification url CDATA #IMPLIED>
+
+<!ELEMENT content_description (#PCDATA)>
+<!ATTLIST content_description url CDATA #IMPLIED>
+
+<!ELEMENT module (description?, module_notification?, external_package*, (manifest | l10n), message_digest* )>
+<!ATTLIST module codenamebase CDATA #REQUIRED
+                 homepage     CDATA #IMPLIED
+                 distribution CDATA #REQUIRED
+                 license      CDATA #IMPLIED
+                 downloadsize CDATA #REQUIRED
+                 needsrestart (true|false) #IMPLIED
+                 moduleauthor CDATA #IMPLIED
+                 releasedate  CDATA #IMPLIED
+                 global       (true|false) #IMPLIED
+                 targetcluster CDATA #IMPLIED
+                 preferredupdate (true|false) #IMPLIED
+                 eager (true|false) #IMPLIED
+                 autoload (true|false) #IMPLIED>
+
+<!ELEMENT description (#PCDATA)>
+
+<!ELEMENT module_notification (#PCDATA)>
+
+<!ELEMENT external_package EMPTY>
+<!ATTLIST external_package
+                 name CDATA #REQUIRED
+                 target_name  CDATA #REQUIRED
+                 start_url    CDATA #REQUIRED
+                 description  CDATA #IMPLIED>
+
+<!ELEMENT manifest EMPTY>
+<!ATTLIST manifest OpenIDE-Module CDATA #REQUIRED
+                   OpenIDE-Module-Name CDATA #REQUIRED
+                   OpenIDE-Module-Specification-Version CDATA #REQUIRED
+                   OpenIDE-Module-Implementation-Version CDATA #IMPLIED
+                   OpenIDE-Module-Module-Dependencies CDATA #IMPLIED
+                   OpenIDE-Module-Package-Dependencies CDATA #IMPLIED
+                   OpenIDE-Module-Java-Dependencies CDATA #IMPLIED
+                   OpenIDE-Module-IDE-Dependencies CDATA #IMPLIED
+                   OpenIDE-Module-Short-Description CDATA #IMPLIED
+                   OpenIDE-Module-Long-Description CDATA #IMPLIED
+                   OpenIDE-Module-Display-Category CDATA #IMPLIED
+                   OpenIDE-Module-Provides CDATA #IMPLIED
+                   OpenIDE-Module-Requires CDATA #IMPLIED
+                   OpenIDE-Module-Recommends CDATA #IMPLIED
+                   OpenIDE-Module-Needs CDATA #IMPLIED
+                   AutoUpdate-Show-In-Client (true|false) #IMPLIED
+                   AutoUpdate-Essential-Module (true|false) #IMPLIED
+                   OpenIDE-Module-Fragment-Host CDATA #IMPLIED>
+
+<!ELEMENT l10n EMPTY>
+<!ATTLIST l10n   langcode             CDATA #IMPLIED
+                 brandingcode         CDATA #IMPLIED
+                 module_spec_version  CDATA #IMPLIED
+                 module_major_version CDATA #IMPLIED
+                 OpenIDE-Module-Name  CDATA #IMPLIED
+                 OpenIDE-Module-Long-Description CDATA #IMPLIED>
+
+<!ELEMENT license (#PCDATA)>
+<!ATTLIST license name CDATA #REQUIRED
+                  url  CDATA #IMPLIED>
+
+<!ELEMENT message_digest EMPTY>
+<!ATTLIST message_digest algorithm CDATA #REQUIRED
+                         value     CDATA #REQUIRED>
\ No newline at end of file
diff --git a/platform/autoupdate.services/manifest.mf b/platform/autoupdate.services/manifest.mf
index 0948f17..c9df2d4 100644
--- a/platform/autoupdate.services/manifest.mf
+++ b/platform/autoupdate.services/manifest.mf
@@ -1,7 +1,7 @@
 Manifest-Version: 1.0
 OpenIDE-Module: org.netbeans.modules.autoupdate.services
 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/autoupdate/services/resources/Bundle.properties
-OpenIDE-Module-Specification-Version: 1.60
+OpenIDE-Module-Specification-Version: 1.62
 OpenIDE-Module-Layer: org/netbeans/modules/autoupdate/services/resources/layer.xml
 AutoUpdate-Show-In-Client: false
 AutoUpdate-Essential-Module: true
diff --git a/platform/autoupdate.services/src/org/netbeans/api/autoupdate/UpdateUnitProvider.java b/platform/autoupdate.services/src/org/netbeans/api/autoupdate/UpdateUnitProvider.java
index 80d17d2..92f2e8c 100644
--- a/platform/autoupdate.services/src/org/netbeans/api/autoupdate/UpdateUnitProvider.java
+++ b/platform/autoupdate.services/src/org/netbeans/api/autoupdate/UpdateUnitProvider.java
@@ -193,4 +193,27 @@
     public String toString() {
         return super.toString() + "[" + impl + "]";
     }
+
+    /** Sets the trusted flag.
+     *
+     * @see #isTrusted
+     * @param trusted
+     * @since 1.62
+     */
+    public void setTrusted (boolean trusted) {
+        impl.setTrusted(trusted);
+    }
+
+    /** Returns <code>true</code> if the provider is considered to be trusted.
+     * If {@link UpdateItem} are provided by a trusted {@code UpdateUnitProvider}
+     * and can be linked to it (for example because the provider provides
+     * checksums in the catalog), they are considered trusted by the Autoupdate
+     * mechanism and will not trigger certificate warnings.
+     *
+     * @return trusted flag
+     * @since 1.62
+     */
+    public boolean isTrusted () {
+        return impl.isTrusted();
+    }
 }
diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/InstallSupportImpl.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/InstallSupportImpl.java
index 5098c28..61f7ba9 100644
--- a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/InstallSupportImpl.java
+++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/InstallSupportImpl.java
@@ -28,13 +28,14 @@
 import java.net.URL;
 import java.net.URLConnection;
 import java.net.UnknownHostException;
+import java.security.CodeSigner;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.cert.Certificate;
+import java.security.cert.TrustAnchor;
 import java.util.*;
 import java.util.concurrent.*;
 import java.util.concurrent.atomic.AtomicLong;
-import java.util.jar.Attributes;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
 import java.util.logging.Level;
@@ -48,10 +49,12 @@
 import org.netbeans.api.autoupdate.OperationSupport.Restarter;
 import org.netbeans.api.progress.ProgressHandle;
 import org.netbeans.modules.autoupdate.updateprovider.AutoupdateInfoParser;
+import org.netbeans.modules.autoupdate.updateprovider.MessageDigestValue;
 import org.netbeans.modules.autoupdate.updateprovider.ModuleItem;
 import org.netbeans.modules.autoupdate.updateprovider.NetworkAccess;
 import org.netbeans.modules.autoupdate.updateprovider.NetworkAccess.Task;
 import org.netbeans.modules.autoupdate.updateprovider.UpdateItemImpl;
+import org.netbeans.spi.autoupdate.KeyStoreProvider;
 import org.netbeans.spi.autoupdate.UpdateItem;
 import org.netbeans.updater.ModuleDeactivator;
 import org.netbeans.updater.ModuleUpdater;
@@ -66,6 +69,8 @@
 import org.openide.util.NbCollections;
 import org.xml.sax.SAXException;
 
+import static org.netbeans.modules.autoupdate.services.Utilities.VERIFICATION_RESULT_COMPARATOR;
+
 /**
  *
  * @author Jiri Rechtacek
@@ -924,9 +929,10 @@
             String label) throws MalformedURLException, IOException {
         
         OpenConnectionListener listener = new OpenConnectionListener(source);
-        final Task task = NetworkAccess.createNetworkAcessTask(source,
+        final Task task = NetworkAccess.createNetworkAccessTask(source,
                 AutoupdateSettings.getOpenConnectionTimeout(),
-                listener);
+                listener,
+                false);
         new Thread(new Runnable() {
             @SuppressWarnings("SleepWhileInLoop")
             @Override
@@ -1010,7 +1016,7 @@
             //        + ") of is equal to estimatedSize (" + estimatedSize + ").";
             if (estimatedSize != increment) {
                 LOG.log (Level.FINEST, "Increment (" + increment + ") of is not equal to estimatedSize (" + estimatedSize + ").");
-            }            
+            }
         } catch (IOException ioe) {
             LOG.log (Level.INFO, "Writing content of URL " + source + " failed.", ioe);
         } finally {
@@ -1022,6 +1028,7 @@
                 LOG.log (Level.INFO, ioe.getMessage (), ioe);
             }
         }
+
         if (contentLength != -1 && increment != contentLength) {
             if(canceled) {
                 LOG.log(Level.INFO, "Download of " + source + " was cancelled");
@@ -1049,29 +1056,98 @@
     }
     
     private int verifyNbm (UpdateElement el, File nbmFile, ProgressHandle progress, int verified) throws OperationException {
-        String res;
+        UpdateElementImpl impl = Trampoline.API.impl(el);
+
+        modified.remove(impl);
+        trusted.remove(impl);
+        signedVerified.remove(impl);
+        signedUnverified.remove(impl);
+
+        String res = null;
         try {
             // get trusted certificates
-            List<Certificate> trustedCerts = new ArrayList<Certificate> ();
-            for (KeyStore ks : Utilities.getKeyStore ()) {
-                trustedCerts.addAll (Utilities.getCertificates (ks));
+            Set<Certificate> trustedCerts = new HashSet<> ();
+            Set<Certificate> validationCerts = new HashSet<>();
+            Set<TrustAnchor> trustedCACerts = new HashSet<>();
+            Set<TrustAnchor> validationCACerts = new HashSet<>();
+            for (KeyStore ks : Utilities.getKeyStore (KeyStoreProvider.TrustLevel.TRUST)) {
+                trustedCerts.addAll(Utilities.getCertificates(ks));
+            }
+            for (KeyStore ks : Utilities.getKeyStore (KeyStoreProvider.TrustLevel.VALIDATE)) {
+                validationCerts.addAll(Utilities.getCertificates(ks));
+            }
+            for (KeyStore ks : Utilities.getKeyStore (KeyStoreProvider.TrustLevel.TRUST_CA)) {
+                trustedCACerts.addAll(Utilities.getTrustAnchor(ks));
+            }
+            for (KeyStore ks : Utilities.getKeyStore (KeyStoreProvider.TrustLevel.VALIDATE_CA)) {
+                validationCACerts.addAll(Utilities.getTrustAnchor(ks));
             }
             // load user certificates
             KeyStore ks = Utilities.loadKeyStore ();
             if (ks != null) {
-                trustedCerts.addAll (Utilities.getCertificates (ks));
+                trustedCerts.addAll(Utilities.getCertificates(ks));
             }
-            
+
             verified += el.getDownloadSize ();
             if (progress != null) {
                 progress.progress (el.getDisplayName (), verified < wasDownloaded ? verified : wasDownloaded);
             }
-            Collection<Certificate> nbmCerts = Utilities.getNbmCertificates (nbmFile);
-            if (nbmCerts != null && nbmCerts.size () > 0) {
-                certs.put (el, nbmCerts);
+
+            {
+                MessageDigestChecker mdChecker = new MessageDigestChecker(impl.getMessageDigests());
+                byte[] buffer = new byte[102400];
+                int read;
+                try(FileInputStream fis = new FileInputStream(nbmFile)) {
+                    while((read = fis.read(buffer)) > 0) {
+                        mdChecker.update(buffer, 0, read);
+                    }
+                }
+                if(!mdChecker.validate()) {
+                    for (String algorithm : mdChecker.getFailingHashes()) {
+                        LOG.log(Level.INFO,
+                            "Failed to validate message digest for ''{0}'' expected ''{1}'' got ''{2}''",
+                            new Object[]{
+                                nbmFile.getAbsolutePath(),
+                                mdChecker.getExpectedHashAsString(algorithm),
+                                mdChecker.getCalculatedHashAsString(algorithm)
+                            });
+                    }
+                    res = Utilities.MODIFIED;
+                } else if (mdChecker.isDigestAvailable() && impl.isCatalogTrusted()) {
+                    res = Utilities.TRUSTED;
+                }
             }
-            res = Utilities.verifyCertificates(nbmCerts, trustedCerts);
-            UpdateElementImpl impl = Trampoline.API.impl(el);
+
+            if(res == null) {
+                try {
+                    Collection<CodeSigner> nbmCerts = Utilities.getNbmCertificates(nbmFile);
+                    if (nbmCerts == null) {
+                        res = Utilities.N_A;
+                    } else if (nbmCerts.isEmpty()) {
+                        res = Utilities.UNSIGNED;
+                    } else {
+                        // Iterate all certpaths that can be considered for the NBM
+                        // choose the certpath, that has the highest trust level
+                        // TRUSTED -> SIGNATURE_VERIFIED -> SIGNATURE_UNVERIFIED
+                        // or comes first
+                        for (CodeSigner cs : nbmCerts) {
+                            String localRes = Utilities.verifyCertificates(cs, trustedCerts, trustedCACerts, validationCerts, validationCACerts);
+                            // If there is no previous result or if the local
+                            // verification yielded a better result than the
+                            // previous result, replace it
+                            if (res == null
+                                || VERIFICATION_RESULT_COMPARATOR.compare(res, localRes) > 0) {
+                                res = localRes;
+                                certs.put(el, (List<Certificate>) cs.getSignerCertPath().getCertificates());
+                            }
+                        }
+                    }
+                } catch (SecurityException ex) {
+                    LOG.log(Level.INFO, "The content of the jar/nbm has been modified or certificate paths were inconsistent - " + ex.getMessage(), ex);
+                    res = Utilities.MODIFIED;
+                }
+            }
+
             if (res != null) {
                 switch (res) {
                     case Utilities.MODIFIED:
diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/MessageDigestChecker.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/MessageDigestChecker.java
new file mode 100644
index 0000000..53d3eaf
--- /dev/null
+++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/MessageDigestChecker.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.autoupdate.services;
+
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.netbeans.modules.autoupdate.updateprovider.MessageDigestValue;
+
+public class MessageDigestChecker {
+
+    private static final Logger LOG = Logger.getLogger(MessageDigestChecker.class.getName());
+
+    private final Map<String,MessageDigest> messageDigest = new HashMap<>();
+    private final Map<String,byte[]> exptectedResult = new HashMap<>();
+    private final Map<String,byte[]> calculatedResult = new HashMap<>();
+    private Boolean overallValid = null;
+
+    public MessageDigestChecker(Collection<MessageDigestValue> referenceHashes) {
+        if (referenceHashes != null) {
+            for (MessageDigestValue h : referenceHashes) {
+                try {
+                    MessageDigest md = MessageDigest.getInstance(h.getAlgorithm());
+                    messageDigest.put(h.getAlgorithm(), md);
+                    exptectedResult.put(h.getAlgorithm(), hexDecode(h.getValue()));
+                } catch (NoSuchAlgorithmException ex) {
+                    LOG.log(Level.FINE, "Unsupported Hash Algorithm: {0}", h.getAlgorithm());
+                }
+            }
+        }
+    }
+
+    public void update(byte[] data) {
+        ensureValidateNotCalled();
+        for(MessageDigest md: messageDigest.values()) {
+            md.update(data);
+        }
+    }
+
+    public void update(byte[] data, int offset, int len) {
+        ensureValidateNotCalled();
+        for(MessageDigest md: messageDigest.values()) {
+            md.update(data, offset, len);
+        }
+    }
+
+    public void update(byte data) {
+        ensureValidateNotCalled();
+        for(MessageDigest md: messageDigest.values()) {
+            md.update(data);
+        }
+    }
+
+    private void ensureValidateNotCalled() throws IllegalStateException {
+        if(overallValid != null) {
+            throw new IllegalStateException("update must not be called after validate is invoked");
+        }
+    }
+
+    private void ensureValidateCalled() throws IllegalStateException {
+        if(overallValid == null) {
+            throw new IllegalStateException("This method must not be called before validate is invoked");
+        }
+    }
+
+    public boolean isDigestAvailable() {
+        return ! messageDigest.isEmpty();
+    }
+
+    public boolean validate() throws IOException {
+        if (overallValid == null) {
+            overallValid = true;
+            for (Entry<String, MessageDigest> e : messageDigest.entrySet()) {
+                String algorithm = e.getKey();
+                calculatedResult.put(algorithm, e.getValue().digest());
+                boolean localValid = Arrays.equals(
+                    exptectedResult.get(algorithm),
+                    calculatedResult.get(algorithm));
+                overallValid &= localValid;
+            }
+        }
+        return overallValid;
+    }
+
+    public List<String> getFailingHashes() {
+        ensureValidateCalled();
+        List<String> result = new ArrayList<>();
+        for(String algorithm: messageDigest.keySet()) {
+            if(! Arrays.equals(
+                exptectedResult.get(algorithm),
+                calculatedResult.get(algorithm))) {
+                result.add(algorithm);
+            }
+        }
+        return result;
+    }
+
+    public String getExpectedHashAsString(String algorithm) {
+        ensureValidateCalled();
+        return hexEncode(exptectedResult.get(algorithm));
+    }
+
+    public String getCalculatedHashAsString(String algorithm) {
+        ensureValidateCalled();
+        return hexEncode(calculatedResult.get(algorithm));
+    }
+
+    public static String hexEncode(byte[] input) {
+        StringBuilder sb = new StringBuilder(input.length * 2);
+        for(byte b: input) {
+            sb.append(Character.forDigit((b & 0xF0) >> 4, 16));
+            sb.append(Character.forDigit((b & 0x0F), 16));
+        }
+        return sb.toString();
+    }
+
+    public static byte[] hexDecode(String input) {
+        int length = input.length() / 2;
+        byte[] result = new byte[length];
+        for(int i = 0; i < length; i++) {
+            int b = Character.digit(input.charAt(i * 2), 16) << 4;
+            b |= Character.digit(input.charAt(i * 2 + 1), 16);
+            result[i] = (byte) (b & 0xFF);
+        }
+        return result;
+    }
+}
diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/UpdateElementImpl.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/UpdateElementImpl.java
index 7cc580c..31a2688 100644
--- a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/UpdateElementImpl.java
+++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/UpdateElementImpl.java
@@ -19,10 +19,12 @@
 
 package org.netbeans.modules.autoupdate.services;
 
+import java.util.ArrayList;
 import java.util.List;
 import org.netbeans.api.autoupdate.UpdateElement;
 import org.netbeans.api.autoupdate.UpdateManager;
 import org.netbeans.api.autoupdate.UpdateUnit;
+import org.netbeans.modules.autoupdate.updateprovider.MessageDigestValue;
 import org.netbeans.modules.autoupdate.updateprovider.InstallInfo;
 import org.netbeans.modules.autoupdate.updateprovider.UpdateItemImpl;
 import org.openide.modules.ModuleInfo;
@@ -35,8 +37,15 @@
 public abstract class UpdateElementImpl extends Object {
     private UpdateUnit unit;
     private UpdateElement element;
-    
-    public UpdateElementImpl (UpdateItemImpl item, String providerName) {}
+    private List<MessageDigestValue> messageDigests = new ArrayList<>();
+    private boolean catalogTrusted = false;
+
+    public UpdateElementImpl (UpdateItemImpl item, String providerName) {
+        if(item.getMessageDigests() != null) {
+            messageDigests.addAll(item.getMessageDigests());
+        }
+        this.catalogTrusted = item.isCatalogTrusted();
+    }
     
     public UpdateUnit getUpdateUnit () {
         return unit;
@@ -99,4 +108,20 @@
     // XXX: try to rid of this
     public abstract InstallInfo getInstallInfo ();
 
+    public List<MessageDigestValue> getMessageDigests() {
+        return messageDigests;
+    }
+
+    public void setMessageDigests(List<MessageDigestValue> messageDigests) {
+        this.messageDigests = messageDigests;
+    }
+
+    public boolean isCatalogTrusted() {
+        return catalogTrusted;
+    }
+
+    public void setCatalogTrusted(boolean catalogTrusted) {
+        this.catalogTrusted = catalogTrusted;
+    }
+
 }
diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/UpdateUnitFactory.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/UpdateUnitFactory.java
index 646df0a..529ada0 100644
--- a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/UpdateUnitFactory.java
+++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/UpdateUnitFactory.java
@@ -113,7 +113,7 @@
 
         for (UpdateUnitProvider up : updates) {
             UpdateUnitProviderImpl impl = Trampoline.API.impl (up);
-            
+
             // append units from provider
             mappedImpl = appendUpdateItems (mappedImpl, impl.getUpdateProvider ());
             reportRunTime ("AppendUpdateItems for " + impl.getUpdateProvider ().getDisplayName ());
@@ -126,7 +126,7 @@
         //TODO: this call should be forced not to be called from AWT
         //assert !SwingUtilities.isEventDispatchThread();
         resetRunTime ("Measuring UpdateUnitFactory.getUpdateUnits (" + provider.getDisplayName () + ")"); // NOI18N
-        
+
         // append units from provider
         Map<String, UpdateUnit> temp = appendUpdateItems (new HashMap<String, UpdateUnit> (), provider);
         reportRunTime ("Get appendUpdateItems for " + provider.getDisplayName ());
@@ -143,6 +143,8 @@
     Map<String, UpdateUnit> appendUpdateItems (Map<String, UpdateUnit> originalUnits, UpdateProvider provider) {
         assert originalUnits != null : "Map of original UnitImpl cannot be null";
 
+        boolean trusted = UpdateUnitProviderImpl.loadTrusted(provider);
+
         Map<String, UpdateItem> items;
         try {
             items = provider.getUpdateItems ();
@@ -155,7 +157,7 @@
         
         // append updates
         for (String simpleItemId : items.keySet ()) {
-            
+
             UpdateElement updateEl = null;
             try {
 
@@ -204,6 +206,7 @@
 
             // add element to map
             if (updateEl != null) {
+                Trampoline.API.impl(updateEl).setCatalogTrusted(trusted);
                 addElement (originalUnits, updateEl, provider);
             }
         }
@@ -282,7 +285,7 @@
         
         // set UpdateUnit into element
         elImpl.setUpdateUnit (unit);
-        
+
     }
     
     private UpdateUnit mergeInstalledUpdateUnit (UpdateUnit uu) {
diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/UpdateUnitProviderImpl.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/UpdateUnitProviderImpl.java
index 102ee99..adc8f20 100644
--- a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/UpdateUnitProviderImpl.java
+++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/UpdateUnitProviderImpl.java
@@ -31,6 +31,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.prefs.BackingStoreException;
@@ -68,6 +69,7 @@
     private static final String URL = "url";
     private static final String DISPLAY_NAME = "displayName";    
     private static final String ENABLED = "enabled";
+    private static final String TRUSTED = "trusted";
     private static final String CATEGORY_NAME = "categoryName";
     private static final LookupListenerImpl UPDATE_PROVIDERS = new LookupListenerImpl();
             
@@ -133,7 +135,15 @@
     public void setProviderURL (URL url) {
         storeUrl (getUpdateProvider (), url);
     }
-    
+
+    public boolean isTrusted() {
+        return loadTrusted(getUpdateProvider());
+    }
+
+    public void setTrusted(Boolean trusted) {
+        storeTrusted(getUpdateProvider(), trusted);
+    }
+
     public List<UpdateUnit> getUpdateUnits (UpdateManager.TYPE... types) {
         return UpdateManagerImpl.getUpdateUnits (getUpdateProvider (), types);
     }
@@ -343,6 +353,7 @@
         String toUrl = providerPreferences.get (URL, providerPreferences.get (AutoupdateCatalogFactory.ORIGINAL_URL, null));
         String displayName = providerPreferences.get (DISPLAY_NAME, providerPreferences.get (AutoupdateCatalogFactory.ORIGINAL_DISPLAY_NAME, codeName));
         String categoryName = providerPreferences.get (CATEGORY_NAME, providerPreferences.get (AutoupdateCatalogFactory.ORIGINAL_CATEGORY_NAME, CATEGORY.COMMUNITY.name()));
+        Boolean trusted = providerPreferences.getBoolean(TRUSTED, providerPreferences.getBoolean(AutoupdateCatalogFactory.ORIGINAL_TRUSTED, false));
         CATEGORY c;
         try {
             c = CATEGORY.valueOf(categoryName);
@@ -368,7 +379,9 @@
         } catch (MalformedURLException mue) {
             assert false : mue;
         }
-        return new AutoupdateCatalogProvider (codeName, displayName, url, pc);
+        AutoupdateCatalogProvider acp = new AutoupdateCatalogProvider (codeName, displayName, url, pc);
+        acp.setTrusted(trusted);
+        return acp;
     }
     
     private static boolean loadState (String codename) {
@@ -461,6 +474,32 @@
         }
     }
 
+   static boolean loadTrusted (UpdateProvider p) {
+        Preferences providerPreferences = getPreferences ().node (p.getName ());
+        assert providerPreferences != null : "Preferences node " + p.getName () + " found.";
+
+        Boolean trusted = null;
+        if (p instanceof AutoupdateCatalogProvider) {
+            trusted = ((AutoupdateCatalogProvider) p).isTrusted();
+        }
+        if(trusted == null) {
+            trusted = false;
+        }
+        return providerPreferences.getBoolean(TRUSTED, trusted);
+    }
+
+    private static void storeTrusted (UpdateProvider p, Boolean trusted) {
+        Preferences providerPreferences = getPreferences ().node (p.getName ());
+        assert providerPreferences != null : "Preferences node " + p.getName () + " found.";
+
+        // store only if differs
+        if (trusted == null) {
+            providerPreferences.remove (TRUSTED);
+        } else {
+            providerPreferences.putBoolean(TRUSTED, trusted);
+        }
+    }
+
     private static class LookupListenerImpl implements LookupListener {
         final Lookup.Result<UpdateProvider> result = Lookup.getDefault ().lookupResult(UpdateProvider.class);
         
diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/Utilities.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/Utilities.java
index f5d886b..fdef5f2 100644
--- a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/Utilities.java
+++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/Utilities.java
@@ -22,20 +22,18 @@
 import java.io.*;
 import java.lang.ref.Reference;
 import java.lang.ref.WeakReference;
+import java.security.CodeSigner;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
-import java.security.Principal;
-import java.security.Security;
-import java.security.cert.CertPath;
 import java.security.cert.CertPathValidator;
 import java.security.cert.CertPathValidatorException;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.PKIXCertPathValidatorResult;
 import java.security.cert.PKIXParameters;
+import java.security.cert.PKIXRevocationChecker;
+import java.security.cert.PKIXRevocationChecker.Option;
 import java.security.cert.TrustAnchor;
 import java.security.cert.X509Certificate;
 import java.text.ParseException;
@@ -46,6 +44,7 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.prefs.Preferences;
+import java.util.regex.Pattern;
 import org.netbeans.Module;
 import org.netbeans.ModuleManager;
 import org.netbeans.api.autoupdate.UpdateElement;
@@ -58,6 +57,7 @@
 import org.netbeans.modules.autoupdate.updateprovider.InstalledModuleProvider;
 import org.netbeans.modules.autoupdate.updateprovider.UpdateItemImpl;
 import org.netbeans.spi.autoupdate.KeyStoreProvider;
+import org.netbeans.spi.autoupdate.KeyStoreProvider.TrustLevel;
 import org.netbeans.spi.autoupdate.UpdateItem;
 import org.netbeans.updater.ModuleDeactivator;
 import org.netbeans.updater.ModuleUpdater;
@@ -102,135 +102,160 @@
     private static final String USER_KS_KEY = "userKS";
     private static final String USER_KS_FILE_NAME = "user.ks";
     private static final String KS_USER_PASSWORD = "open4user";
-    private static Lookup.Result<KeyStoreProvider> result;
+    private static Lookup.Result<KeyStoreProvider> keyStoreLookupResult;
+    private static Map<TrustLevel,List<KeyStore>> keystoreCache = Collections.synchronizedMap(new HashMap<>());
     private static final Logger err = Logger.getLogger(Utilities.class.getName ());
-    
-    
-    public static Collection<KeyStore> getKeyStore () {
-        if (result == null) {            
-            result = Lookup.getDefault ().lookupResult (KeyStoreProvider.class);
-            result.addLookupListener (new KeyStoreProviderListener ());
-        }
-        Collection<? extends KeyStoreProvider> c = result.allInstances ();
-        if (c == null || c.isEmpty ()) {
-            return Collections.emptyList ();
-        }
-        List<KeyStore> kss = new ArrayList<>();
-        
-        for (KeyStoreProvider provider : c) {
-            KeyStore ks = provider.getKeyStore ();
-            if (ks != null) {
-                kss.add (ks);
-            }
-        }
-        
-        return kss;
-    }
-    
-    public static String verifyCertificates(Collection<Certificate> archiveCertificates, Collection<Certificate> trustedCertificates) {
-        if (archiveCertificates == null) {
-            return N_A;
-        }       
-        if (!archiveCertificates.isEmpty()) {
-            Collection<Certificate> c = new HashSet<>(trustedCertificates);
-            c.retainAll(archiveCertificates);
-            if (c.isEmpty()) {                
-                Map<Principal, X509Certificate> certSubjectsMap = new HashMap<>();               
-                Set<Principal> certIssuersSet = new HashSet<>();
-                for (Certificate cert : archiveCertificates) {
-                    if (cert != null) {
-                        X509Certificate x509Cert = (X509Certificate) cert;
-                        certSubjectsMap.put(x509Cert.getSubjectDN(), x509Cert);
-                        if (x509Cert.getIssuerDN() != null) {
-                            certIssuersSet.add(x509Cert.getIssuerDN());
-                        }
-                    }
-                }
-                
-                Map<X509Certificate, X509Certificate> candidates = new HashMap<>();
-                    
-                for (Principal p : certSubjectsMap.keySet()) {
-                    // cert chain may not be ordered - trust anchor could before certificate itself
-                    if (certIssuersSet.contains(p)) {
-                        continue;
-                    }
-                    
-                    X509Certificate cert = certSubjectsMap.get(p);
-                
-                    Principal tap = cert.getIssuerDN();
-                    if (tap != null) {
-                        X509Certificate tempTrustAnchor = certSubjectsMap.get(tap);
-                        if (tempTrustAnchor != null) {
-                            candidates.put(cert, tempTrustAnchor);
-                        }
-                    }
-                }                                               
 
-                // TRUSTED = 2
-                // SIGNATURE_VERIFIED = 1
-                // SIGNATURE_UNVERIFIED = 0
-                int res = 0;                
-                for (X509Certificate cert : candidates.keySet()) {
-                    X509Certificate trustCert = candidates.get(cert);
-                    PKIXCertPathValidatorResult validResult = null;
-                    try {
-                        CertificateFactory cf = CertificateFactory.getInstance("X.509");
-                        List certList = new ArrayList();
-                        certList.add(cert);
-                        CertPath cp = cf.generateCertPath(certList);
-                        TrustAnchor trustAnchor = new TrustAnchor(trustCert, null);
-                        PKIXParameters params = new PKIXParameters(Collections.singleton(trustAnchor));
-                        params.setRevocationEnabled(true);
-                        Security.setProperty("ocsp.enable", "true");
-                        System.setProperty("com.sun.security.enableCRLDP", "true"); // CRL fallback
-                        CertPathValidator cpv = CertPathValidator.getInstance("PKIX");
-                        validResult = (PKIXCertPathValidatorResult) cpv.validate(cp, params);
-                    } catch (CertificateException | InvalidAlgorithmParameterException | NoSuchAlgorithmException ex) {
-                        // CertificateException - Should not get here - "X.509" is proper certificate type
-                        // InvalidAlgorithmParameterException - Should not get here - trustAnchor cannot be null -> collection cannot be empty
-                        // NoSuchAlgorithmException - Should not get here - "PKIX" is proper algorythm
-                        err.log(Level.SEVERE, "Certificate verification failed - " + ex.getMessage(), ex);
-                        //SIGNATURE_UNVERIFIED - result = 0;
-                    } catch (CertPathValidatorException ex) {
-                        // CertPath cannot be validated
-                        err.log(Level.INFO, "Cannot validate certificate path - " + ex.getMessage(), ex);
-                        //SIGNATURE_UNVERIFIED - result = 0;
-                    } catch (SecurityException ex) {
-                        // When jar/nbm correctly signed, but content modified                    
-                        err.log(Level.INFO, "The content of the jar/nbm has been modified - " + ex.getMessage(), ex);
-                        return MODIFIED;                    
-                    }
-
-                    if (validResult != null) {
-                        String certDNName = cert.getSubjectDN().getName();
-                        if (certDNName.contains("CN=\"Oracle America, Inc.\"")
-                                && (certDNName.contains("OU=Software Engineering") || certDNName.contains("OU=Code Signing Bureau"))) {
-                            res = 2;
-                            break;
-                        } else {
-                            res = 1;
-                        }                        
-                    }
-                }
-                
-                switch (res) {
-                    case 2:
-                        return TRUSTED;
-                    case 1:
-                        return SIGNATURE_VERIFIED;
-                    default:
-                        return SIGNATURE_UNVERIFIED;                    
-                }
-            } else {
-                // signed by trusted certificate stored in user's keystore od ide.ks
-                return TRUSTED;
-            }
+    public static Collection<KeyStore> getKeyStore (TrustLevel trustLevel) {
+        if (keyStoreLookupResult == null) {
+            keyStoreLookupResult = Lookup.getDefault().lookupResult(KeyStoreProvider.class);
+            keyStoreLookupResult.addLookupListener(new KeyStoreProviderListener());
         }
-        return UNSIGNED;
+
+        List<KeyStore> result = keystoreCache.get(trustLevel);
+
+        if (result == null) {
+            Collection<? extends KeyStoreProvider> c = keyStoreLookupResult.allInstances();
+            if (c == null || c.isEmpty()) {
+                return Collections.emptyList();
+            }
+            List<KeyStore> kss = new ArrayList<>();
+
+            for (KeyStoreProvider provider : c) {
+                KeyStore ks = provider.getKeyStore();
+                if (ks != null) {
+                    kss.add(ks);
+                }
+            }
+
+            result = Collections.unmodifiableList(kss);
+
+            keystoreCache.put(trustLevel, result);
+        }
+
+        return result;
     }
-    
+
+    /**
+     *
+     * @param archiveCertPath
+     * @param trustedCertificates
+     * @return
+     */
+    public static String verifyCertificates(CodeSigner archiveCertPath,
+        Collection<Certificate> trustedCertificates,
+        Set<TrustAnchor> trustedCACertificates,
+        Collection<Certificate> validateCertificates,
+        Set<TrustAnchor> validationCACertificates) {
+        assert archiveCertPath != null;
+
+        List<? extends Certificate> archiveCertificates = archiveCertPath.getSignerCertPath().getCertificates();
+
+        if(archiveCertificates.isEmpty()) {
+            return UNSIGNED;
+        }
+
+        // Case 1: We have direct trust into one of the certificates of the
+        //         certificate chain
+        if(isChainTrusted(archiveCertificates, trustedCertificates)) {
+            return TRUSTED;
+        }
+
+        // Case 2: We trust the CA, that issued a certificate - do the
+        //         normal CertPathValidation
+        if(verifyCACertificatePath(trustedCACertificates, archiveCertPath)) {
+            return TRUSTED;
+        }
+
+        // Case 3: We have a list of certificates, that we directly mark as
+        //         valid
+        if(isChainTrusted(archiveCertificates, validateCertificates)) {
+            return SIGNATURE_VERIFIED;
+        }
+
+        // Case 4: We trust the CA to do validation
+        if (verifyCACertificatePath(validationCACertificates, archiveCertPath)) {
+            return SIGNATURE_VERIFIED;
+        }
+
+        // Case 5: File is not signed
+        return SIGNATURE_UNVERIFIED;
+    }
+
+    private static boolean verifyCACertificatePath(Set<TrustAnchor> trustedCACertificates, CodeSigner archiveCertPath) {
+        if(trustedCACertificates.isEmpty()) {
+            return false;
+        }
+        try {
+            CertPathValidator cpv = CertPathValidator.getInstance("PKIX");
+            PKIXParameters verificationParameters = new PKIXParameters(trustedCACertificates);
+            PKIXRevocationChecker rc = (PKIXRevocationChecker) cpv.getRevocationChecker();
+            rc.setOptions(EnumSet.of(Option.SOFT_FAIL));
+            verificationParameters.addCertPathChecker(rc);
+            if (archiveCertPath.getTimestamp() != null) {
+                cpv.validate(archiveCertPath.getSignerCertPath(), verificationParameters);
+                verificationParameters.setDate(archiveCertPath.getTimestamp().getTimestamp());
+            }
+            // validate raises a CertPathValidatorException if validation failed
+            cpv.validate(archiveCertPath.getSignerCertPath(), verificationParameters);
+            return true;
+        } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException ex) {
+            // InvalidAlgorithmParameterException - Should not get here - trustAnchor cannot be null -> collection cannot be empty
+            // NoSuchAlgorithmException - Should not get here - "PKIX" is proper algorythm
+            err.log(Level.SEVERE, "Certificate verification failed - " + ex.getMessage(), ex);
+            //SIGNATURE_UNVERIFIED - result = 0;
+        } catch (CertPathValidatorException ex) {
+            // CertPath cannot be validated
+            err.log(Level.INFO, "Cannot validate certificate path - " + ex.getMessage(), ex);
+            //SIGNATURE_UNVERIFIED - result = 0;
+        }
+        return false;
+    }
+
+    private static boolean isChainTrusted(Collection<? extends Certificate> archiveCertificates, Collection<? extends Certificate> trustedCertificates) {
+        Collection<Certificate> c = new HashSet(trustedCertificates);
+        c.retainAll(archiveCertificates);
+        return ! c.isEmpty();
+    }
+
+    /**
+     * Establishes an order for verificication result strings. Only the values
+     * <ul>
+     * <li>{@code null}</li>
+     * <li>{@link #UNSIGNED}</li>
+     * <li>{@link #SIGNATURE_UNVERIFIED}</li>
+     * <li>{@link #SIGNATURE_VERIFIED}</li>
+     * <li>{@link #TRUSTED}</li>
+     * </ul>
+     *
+     * @param verificationResult1
+     * @param verificationResult2
+     * @return
+     */
+    public static final Comparator<String> VERIFICATION_RESULT_COMPARATOR = new Comparator<String>() {
+        @Override
+        public int compare(String o1, String o2) {
+            int i1 = mapVerificationResultToInt(o1);
+            int i2 = mapVerificationResultToInt(o2);
+            return i1 - i2;
+        }
+    };
+
+    private static int mapVerificationResultToInt(String input) {
+        if(input == null) {
+            return 0;
+        }
+        switch(input) {
+            case UNSIGNED: return 1;
+            case SIGNATURE_UNVERIFIED: return 2;
+            case SIGNATURE_VERIFIED: return 3;
+            case TRUSTED: return 4;
+            default: throw new IllegalArgumentException("Unmappable value: " + input);
+        }
+    }
+
     public static Collection<Certificate> getCertificates (KeyStore keyStore) throws KeyStoreException {
-        Set<Certificate> certs = new HashSet<Certificate> ();
+        Set<Certificate> certs = new HashSet<> ();
         for (String alias: Collections.list (keyStore.aliases ())) {
             Certificate[] certificateChain = keyStore.getCertificateChain(alias);
             if (certificateChain != null) {
@@ -240,33 +265,91 @@
         }
         return certs;
     }
-    
-    public static Collection<Certificate> getNbmCertificates (File nbmFile) throws IOException {
-        Set<Certificate> certs = new HashSet<Certificate>();
-        JarFile jf = new JarFile(nbmFile);
-        boolean empty = true;
-        try {
-            for (JarEntry entry : Collections.list(jf.entries())) {
-                verifyEntry(jf, entry);
-                if (!entry.getName().startsWith("META-INF/")) {
-                    empty = false;
-                    if (entry.getCertificates() != null) {
-                        certs.addAll(Arrays.asList(entry.getCertificates()));
+
+    public static Collection<TrustAnchor> getTrustAnchor (KeyStore keyStore) throws KeyStoreException {
+        Set<TrustAnchor> certs = new HashSet<> ();
+        for (String alias: Collections.list (keyStore.aliases ())) {
+            Certificate[] certificateChain = keyStore.getCertificateChain(alias);
+            if (certificateChain != null) {
+                for(Certificate cert: certificateChain) {
+                    if(cert instanceof X509Certificate) {
+                        certs.add(new TrustAnchor((X509Certificate) cert, null));
                     }
                 }
             }
-        } finally {
-            jf.close();
+            Certificate aliasCert = keyStore.getCertificate(alias);
+            if(aliasCert instanceof X509Certificate) {
+                certs.add(new TrustAnchor((X509Certificate) aliasCert, null));
+            }
+        }
+        return certs;
+    }
+
+    /**
+     * Get the certpaths that were used to sign the NBM content.
+     *
+     * @param nbmFile
+     * @return collection of CodeSigners, that were used to sign the non-signature
+     * entries of the NBM
+     * @throws IOException
+     * @throws SecurityException if JAR was tampered with or if the certificate
+     *         chains are not consistent
+     */
+    public static Collection<CodeSigner> getNbmCertificates (File nbmFile) throws IOException, SecurityException {
+        Set<CodeSigner> certs = null;
+
+        // Empty means only the MANIFEST.MF is present - special cased to be in
+        // line with established behaviour
+        boolean empty = true;
+
+        // The idea:
+        // - iterate over all JAR entries
+        // - read each entry (as required by the JarFile specificiation for verification)
+        // - extract the certificate paths, that were used to sign the
+        //   entry
+        // - compare it with the previously read entries. If they are not
+        //   identical, raise a SecurityException.
+        //
+        // Excluded from the above algorithm are:
+        // - directory entries
+        // - files that are part of the signature (each entry in META-INF, that
+        //   ends with .SF, .DSA, .RSA or .EC
+        try (JarFile jf = new JarFile(nbmFile)) {
+            for (JarEntry entry : Collections.list(jf.entries())) {
+                verifyEntry(jf, entry);
+                if ((! entry.isDirectory()) && (! isSignatureEntry(entry))) {
+                    if(! entry.getName().equals("META-INF/MANIFEST.MF")) {
+                        empty = false;
+                    }
+                    Set<CodeSigner> entryCerts = new HashSet<>();
+                    CodeSigner[] codeSigners = entry.getCodeSigners();
+                    if (codeSigners != null) {
+                        for (CodeSigner cs : entry.getCodeSigners()) {
+                            entryCerts.add(cs);
+                        }
+                    }
+                    if(certs == null) {
+                        certs = entryCerts;
+                    } else if (! certs.equals(entryCerts)) {
+                        throw new SecurityException("Inconsistent certificate paths used for signing");
+                    }
+                }
+            }
         }
 
         return empty ? null : certs;
     }
-    
+
+    private static final Pattern SIGNATURE_PATTERN = Pattern.compile("META-INF/([^/]+)\\.(SF|DSA|RSA|EC)");
+    private static boolean isSignatureEntry(JarEntry je) {
+	return SIGNATURE_PATTERN.matcher(je.getName()).matches();
+    }
+
     /**
      * @throws SecurityException
      */
     @SuppressWarnings("empty-statement")
-    private static void verifyEntry (JarFile jf, JarEntry je) throws IOException {
+    private static void verifyEntry (JarFile jf, JarEntry je) throws IOException, SecurityException {
         InputStream is = null;
         try {
             is = jf.getInputStream (je);
@@ -283,7 +366,7 @@
         
         @Override
         public void resultChanged (LookupEvent ev) {
-            result = null;
+            keystoreCache.clear();
         }
     }
     
diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/AutoupdateCatalogCache.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/AutoupdateCatalogCache.java
index ab94735..ba81ccd 100644
--- a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/AutoupdateCatalogCache.java
+++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/AutoupdateCatalogCache.java
@@ -216,7 +216,7 @@
 
         DownloadListener nwl = new DownloadListener(sourceUrl, temp, allowZeroSize);
         
-        NetworkAccess.Task task = NetworkAccess.createNetworkAcessTask (sourceUrl, AutoupdateSettings.getOpenConnectionTimeout (), nwl);
+        NetworkAccess.Task task = NetworkAccess.createNetworkAccessTask (sourceUrl, AutoupdateSettings.getOpenConnectionTimeout (), nwl, true);
         task.waitFinished ();
         nwl.notifyException ();
         synchronized(getLock(cache)) {
diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/AutoupdateCatalogFactory.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/AutoupdateCatalogFactory.java
index ae134a1..1f0ec51 100644
--- a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/AutoupdateCatalogFactory.java
+++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/AutoupdateCatalogFactory.java
@@ -62,6 +62,7 @@
     public static final String ORIGINAL_ENABLED = "originalEnabled"; // NOI18N
     public static final String ORIGINAL_CATEGORY_NAME = "originalCategoryName"; // NOI18N
     public static final String ORIGINAL_CATEGORY_ICON_BASE = "originalCategoryIconBase"; // NOI18N
+    public static final String ORIGINAL_TRUSTED = "originalTrusted"; // NOI18N
     
     public static UpdateProvider createUpdateProvider (FileObject fo) {
         String sKey = (String) fo.getAttribute ("url_key"); // NOI18N
@@ -143,6 +144,12 @@
             providerPreferences.putBoolean (ORIGINAL_ENABLED, en);
         }
 
+        Boolean trusted = (Boolean) fo.getAttribute("trusted"); // NOI18N
+        if(trusted != null) {
+            au_catalog.setTrusted(trusted);
+            providerPreferences.putBoolean(ORIGINAL_TRUSTED, trusted);
+        }
+
         return au_catalog;
     }
     
diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/AutoupdateCatalogParser.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/AutoupdateCatalogParser.java
index d95e1b4..a6d737f 100644
--- a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/AutoupdateCatalogParser.java
+++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/AutoupdateCatalogParser.java
@@ -34,7 +34,6 @@
 import java.util.zip.GZIPInputStream;
 import javax.xml.parsers.SAXParser;
 import javax.xml.parsers.SAXParserFactory;
-import org.netbeans.Module;
 import org.netbeans.modules.autoupdate.services.Trampoline;
 import org.netbeans.modules.autoupdate.services.UpdateLicenseImpl;
 import org.netbeans.modules.autoupdate.services.Utilities;
@@ -52,6 +51,7 @@
     private final AutoupdateCatalogProvider provider;
     private final EntityResolver entityResolver;
     private final URI baseUri;
+    private final List<MessageDigestValue> messageDigestsBuffer = new ArrayList<>();
     
     private AutoupdateCatalogParser (Map<String, UpdateItem> items, AutoupdateCatalogProvider provider, URI base) {
         this.items = items;
@@ -111,7 +111,7 @@
     
     private static enum ELEMENTS {
         module_updates, module_group, notification, content_description, module, description,
-        module_notification, external_package, manifest, l10n, license
+        module_notification, external_package, manifest, l10n, license, message_digest
     }
     
     private static final String MODULE_UPDATES_ATTR_TIMESTAMP = "timestamp"; // NOI18N
@@ -149,7 +149,10 @@
     private static final String L10N_ATTR_MODULE_MAJOR_VERSION = "module_major_version"; // NOI18N
     private static final String L10N_ATTR_LOCALIZED_MODULE_NAME = "OpenIDE-Module-Name"; // NOI18N
     private static final String L10N_ATTR_LOCALIZED_MODULE_DESCRIPTION = "OpenIDE-Module-Long-Description"; // NOI18N
-    
+
+    private static final String MESSAGE_DIGEST_ATTR_ALGORITHM = "algorithm"; // NOI18N
+    private static final String MESSAGE_DIGEST_ATTR_VALUE = "value"; // NOI18N
+
     private static String GZIP_EXTENSION = ".gz"; // NOI18N
     private static String XML_EXTENSION = ".xml"; // NOI18N
     private static String GZIP_MIME_TYPE = "application/x-gzip"; // NOI18N
@@ -359,6 +362,8 @@
                 
                 currentLicense.pop ();
                 break;
+            case message_digest:
+                break;
             default:
                 ERR.warning ("Unknown element " + qName);
         }
@@ -454,6 +459,16 @@
                 map.put(attributes.getValue (LICENSE_ATTR_NAME), attributes.getValue (LICENSE_ATTR_URL));
                 currentLicense.push (map);
                 break;
+            case message_digest:
+                ModuleDescriptor desc2 = currentModule.peek ();
+                // At this point the manifest element must have been seen
+                UpdateItem ui = items.get (desc2.getId ());
+                UpdateItemImpl uiImpl = Trampoline.SPI.impl (ui);
+                uiImpl.getMessageDigests().add(new MessageDigestValue(
+                    attributes.getValue(MESSAGE_DIGEST_ATTR_ALGORITHM),
+                    attributes.getValue(MESSAGE_DIGEST_ATTR_VALUE)
+                ));
+                break;
             default:
                 ERR.warning ("Unknown element " + qName);
         }
@@ -511,6 +526,8 @@
         private String catalogDate;
         
         private String fragmentHost;
+
+        private List<MessageDigestValue> hashes;
         
         private static ModuleDescriptor md = null;
         
@@ -608,7 +625,7 @@
             return res;
         }
         
-        private void cleanUp (){
+        public void cleanUp (){
             this.specVersion = null;
             this.mf = null;
             this.notification = null;
diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/AutoupdateCatalogProvider.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/AutoupdateCatalogProvider.java
index a9ea300..326fd11 100644
--- a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/AutoupdateCatalogProvider.java
+++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/AutoupdateCatalogProvider.java
@@ -26,6 +26,7 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import org.netbeans.api.autoupdate.UpdateUnitProvider.CATEGORY;
+import org.netbeans.modules.autoupdate.services.Trampoline;
 import org.netbeans.spi.autoupdate.UpdateItem;
 import org.netbeans.spi.autoupdate.UpdateProvider;
 import org.openide.util.Parameters;
@@ -45,6 +46,7 @@
     private ProviderCategory category;
     private String contentDescription;
     private boolean contentDescriptionInitialized;
+    private boolean trusted;
 
     public AutoupdateCatalogProvider (String name, String displayName, URL updateCenter) {
         this(name, displayName, updateCenter, ProviderCategory.forValue(CATEGORY.COMMUNITY));
@@ -117,6 +119,9 @@
             synchronized(cache.getLock(toParse)) {
                 map = AutoupdateCatalogParser.getUpdateItems (toParse, this);
             }
+            for(UpdateItem ui: map.values()) {
+                UpdateItemImpl impl = Trampoline.SPI.impl(ui);
+            }
             descriptionInitialized = true;
             return map;        
     }
@@ -158,4 +163,12 @@
     public ProviderCategory getProviderCategory() {
         return category;
     }
+
+    public boolean isTrusted() {
+        return trusted;
+    }
+
+    public void setTrusted(boolean trusted) {
+        this.trusted = trusted;
+    }
 }
diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/MessageDigestValue.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/MessageDigestValue.java
new file mode 100644
index 0000000..1351ef9
--- /dev/null
+++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/MessageDigestValue.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.autoupdate.updateprovider;
+
+import java.util.Objects;
+
+public class MessageDigestValue {
+    private String algorithm;
+    private String value;
+
+    public MessageDigestValue() {
+    }
+
+    public MessageDigestValue(String algorithm, String value) {
+        this.algorithm = algorithm;
+        this.value = value;
+    }
+
+    public String getAlgorithm() {
+        return algorithm;
+    }
+
+    public void setAlgorithm(String algorithm) {
+        this.algorithm = algorithm;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 3;
+        hash = 53 * hash + Objects.hashCode(this.algorithm);
+        hash = 53 * hash + Objects.hashCode(this.value);
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final MessageDigestValue other = (MessageDigestValue) obj;
+        if (!Objects.equals(this.algorithm, other.algorithm)) {
+            return false;
+        }
+        if (!Objects.equals(this.value, other.value)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/NetworkAccess.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/NetworkAccess.java
index c79154a..1560eda 100644
--- a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/NetworkAccess.java
+++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/NetworkAccess.java
@@ -24,10 +24,6 @@
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.net.URLConnection;
-import java.security.KeyManagementException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.security.cert.X509Certificate;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
@@ -40,12 +36,6 @@
 import java.util.concurrent.TimeoutException;
 import java.util.logging.Level;
 import java.util.logging.Logger;
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
 import org.openide.util.Cancellable;
 import org.openide.util.NbBundle;
 import org.openide.util.RequestProcessor;
@@ -63,19 +53,20 @@
 
     private NetworkAccess () {}
     
-    public static Task createNetworkAcessTask (URL url, int timeout, NetworkListener networkAcesssListener) {
-        return new Task (url, timeout, networkAcesssListener);
+    public static Task createNetworkAccessTask (URL url, int timeout, NetworkListener networkAccessListener, boolean disableInsecureRedirects) {
+        return new Task (url, timeout, networkAccessListener, disableInsecureRedirects);
     }
     
     public static class Task implements Cancellable {
+        private final ExecutorService es = Executors.newSingleThreadExecutor();
+        private final boolean disableInsecureRedirects;
         private URL url;
         private int timeout;
         private NetworkListener listener;
-        private final ExecutorService es = Executors.newSingleThreadExecutor ();
         private Future<InputStream> connect = null;
         private RequestProcessor.Task rpTask = null;
         
-        private Task (URL url, int timeout, NetworkListener listener) {
+        private Task (URL url, int timeout, NetworkListener listener, boolean disableInsecureRedirects) {
             if (url == null) {
                 throw new IllegalArgumentException ("URL cannot be null.");
             }
@@ -85,6 +76,7 @@
             this.url = url;
             this.timeout = timeout;
             this.listener = listener;
+            this.disableInsecureRedirects = disableInsecureRedirects;
             postTask ();
         }
         
@@ -146,11 +138,7 @@
                 @Override
                 public InputStream call () throws Exception {
                     URLConnection conn = url.openConnection ();
-                    conn.setConnectTimeout (timeout);
-                    conn.setReadTimeout(timeout);
-                    if(conn instanceof HttpsURLConnection){
-                        NetworkAccess.initSSL((HttpsURLConnection) conn);
-                    }
+                    configureConnection(conn, timeout);
 
                     // handle redirection here
                     int redirCount = 0;
@@ -181,13 +169,52 @@
                 }
             };
         }
-        
+
         @Override
         public boolean cancel () {
-            return connect.cancel (true);
+            return connect.cancel(true);
         }
-        
+
+        private URLConnection checkRedirect(URLConnection conn, int timeout) throws IOException {
+            if (conn instanceof HttpURLConnection) {
+                conn.connect();
+                int code = ((HttpURLConnection) conn).getResponseCode();
+                boolean isInsecure = "http".equalsIgnoreCase(conn.getURL().getProtocol());
+                if (code == HttpURLConnection.HTTP_MOVED_TEMP
+                    || code == HttpURLConnection.HTTP_MOVED_PERM) {
+                    // in case of redirection, try to obtain new URL
+                    String redirUrl = conn.getHeaderField("Location"); //NOI18N
+                    if (null != redirUrl && !redirUrl.isEmpty()) {
+                        //create connection to redirected url and substitute original connection
+                        URL redirectedUrl = new URL(redirUrl);
+                        if (disableInsecureRedirects && (!isInsecure) && (!redirectedUrl.getProtocol().equalsIgnoreCase(conn.getURL().getProtocol()))) {
+                            throw new IOException(String.format(
+                                "Redirect from secure URL '%s' to '%s' blocked.",
+                                conn.getURL().toExternalForm(),
+                                redirectedUrl.toExternalForm()
+                            ));
+                        }
+                        URLConnection connRedir = redirectedUrl.openConnection();
+                        connRedir.setRequestProperty("User-Agent", "NetBeans"); // NOI18N
+                        connRedir.setConnectTimeout(timeout);
+                        connRedir.setReadTimeout(timeout);
+                        return connRedir;
+                    }
+                }
+            }
+            return conn;
+        }
+
+        private static void configureConnection(URLConnection conn, int timeout) {
+            if (conn instanceof HttpURLConnection) {
+                ((HttpURLConnection) conn).setInstanceFollowRedirects(false);
+            }
+            conn.setRequestProperty("User-Agent", "NetBeans");
+            conn.setConnectTimeout(timeout);
+            conn.setReadTimeout(timeout);
+        }
     }
+
     private interface SizedConnection extends Callable<InputStream> {
         public int getContentLength();
     }
@@ -197,67 +224,4 @@
         public void accessTimeOut ();
         public void notifyException (Exception x);
     }
-    
-    public static void initSSL(HttpURLConnection httpCon) throws IOException {
-        if (httpCon instanceof HttpsURLConnection) {
-            HttpsURLConnection https = (HttpsURLConnection) httpCon;
-
-            try {
-                TrustManager[] trustAllCerts = new TrustManager[]{
-                    new X509TrustManager() {
-                        @Override
-                        public X509Certificate[] getAcceptedIssuers() {
-                            return new X509Certificate[0];
-                        }
-
-                        @Override
-                        public void checkClientTrusted(X509Certificate[] certs, String authType) {
-                        }
-
-                        @Override
-                        public void checkServerTrusted(X509Certificate[] certs, String authType) {
-                        }
-                    }};
-                SSLContext sslContext = SSLContext.getInstance("SSL"); // NOI18N
-                sslContext.init(null, trustAllCerts, new SecureRandom());
-                https.setHostnameVerifier(new HostnameVerifier() {
-                    @Override
-                    public boolean verify(String hostname, SSLSession session) {
-                        return true;
-                    }
-                });
-                https.setSSLSocketFactory(sslContext.getSocketFactory());
-            } catch (KeyManagementException ex) {
-                throw new IOException(ex);
-            } catch (NoSuchAlgorithmException ex) {
-                throw new IOException(ex);
-            }
-        }
-    }
-
-    private static URLConnection checkRedirect(URLConnection conn, int timeout) throws IOException {
-        if (conn instanceof HttpURLConnection) {
-            conn.connect();
-            int code = ((HttpURLConnection) conn).getResponseCode();
-            if (code == HttpURLConnection.HTTP_MOVED_TEMP
-                    || code == HttpURLConnection.HTTP_MOVED_PERM) {
-                // in case of redirection, try to obtain new URL
-                String redirUrl = conn.getHeaderField("Location"); //NOI18N
-                if (null != redirUrl && !redirUrl.isEmpty()) {
-                    //create connection to redirected url and substitute original conn
-                    URL redirectedUrl = new URL(redirUrl);
-                    URLConnection connRedir = redirectedUrl.openConnection();
-                    // XXX is this neede
-                    connRedir.setRequestProperty("User-Agent", "NetBeans"); // NOI18N
-                    connRedir.setConnectTimeout(timeout);
-                    connRedir.setReadTimeout(timeout);
-                    if (connRedir instanceof HttpsURLConnection) {
-                        NetworkAccess.initSSL((HttpsURLConnection) connRedir);
-                    }
-                    return connRedir;
-                }
-            }
-        }
-        return conn;
-    }
 }
diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/UpdateItemImpl.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/UpdateItemImpl.java
index 0f639dd..30d410c 100644
--- a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/UpdateItemImpl.java
+++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/updateprovider/UpdateItemImpl.java
@@ -19,6 +19,8 @@
 
 package org.netbeans.modules.autoupdate.updateprovider;
 
+import java.util.ArrayList;
+import java.util.List;
 import org.netbeans.modules.autoupdate.services.UpdateLicenseImpl;
 import org.netbeans.spi.autoupdate.UpdateItem;
 
@@ -28,6 +30,8 @@
  */
 public abstract class UpdateItemImpl {
     private UpdateItem originalUpdateItem;
+    private List<MessageDigestValue> messageDigests = new ArrayList<>();
+    private boolean catalogTrusted = false;
 
     /** Creates a new instance of UpdateItemImpl */
     UpdateItemImpl () {
@@ -64,4 +68,20 @@
     public String getFragmentHost() {
         return null;
     }
+
+    public List<MessageDigestValue> getMessageDigests() {
+        return messageDigests;
+    }
+
+    public void setMessageDigests(List<MessageDigestValue> messageDigests) {
+        this.messageDigests = new ArrayList<>(messageDigests);
+    }
+
+    public boolean isCatalogTrusted() {
+        return catalogTrusted;
+    }
+
+    public void setCatalogTrusted(boolean catalogTrusted) {
+        this.catalogTrusted = catalogTrusted;
+    }
 }
diff --git a/platform/autoupdate.services/src/org/netbeans/spi/autoupdate/KeyStoreProvider.java b/platform/autoupdate.services/src/org/netbeans/spi/autoupdate/KeyStoreProvider.java
index 5f2b3ad..e94fb1d 100644
--- a/platform/autoupdate.services/src/org/netbeans/spi/autoupdate/KeyStoreProvider.java
+++ b/platform/autoupdate.services/src/org/netbeans/spi/autoupdate/KeyStoreProvider.java
@@ -29,9 +29,52 @@
  * @author Jiri Rechtacek
  */
 public interface KeyStoreProvider {
-    
+    /**
+     * TrustLevel describes the level of trust, that a {@link KeyStoreProvider}
+     * assigns to the provided keystore.
+     *
+     * @since 1.61
+     */
+    public enum TrustLevel {
+        /**
+         * Unlimited trust - modules signed with certificates in this store
+         * will be installed without further user requests. This level is by
+         * default used for the update centers of the IDE itself.
+         */
+        TRUST,
+        /**
+         * Unlimited trust - modules signed with certificates in this store
+         * will be installed without further user requests. This level is by
+         * default used for the update centers of the IDE itself. It differes
+         * from {@link TRUST} in that, these certificates are subject to a
+         * {@code CertPathValidator}
+         */
+        TRUST_CA,
+        /**
+         * Plugins signed with certificates from this store will show up as
+         * "Signed and valid".
+         */
+        VALIDATE,
+        /**
+         * Plugins signed with certificates created by these certificates
+         * will show up as "Signed and valid". While certificates provided by
+         * {@link VALIDATE} not subject to PKIX checking, these certificates
+         * are run through a {@code CertPathValidator}.
+         */
+        VALIDATE_CA
+    }
+
     /**
      * @return KeyStore
      */
     public KeyStore getKeyStore ();
+
+    /**
+     * @return TrustLevel that is provided by the keystore this
+     *         {@link KeyStoreProvider} provides
+     * @since 1.61
+     */
+    default TrustLevel getTrustLevel() {
+        return TrustLevel.TRUST;
+    }
 }
diff --git a/platform/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/MessageDigestCheckerTest.java b/platform/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/MessageDigestCheckerTest.java
new file mode 100644
index 0000000..79af4ab
--- /dev/null
+++ b/platform/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/MessageDigestCheckerTest.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.autoupdate.services;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class MessageDigestCheckerTest {
+
+    public MessageDigestCheckerTest() {
+    }
+
+    @Test
+    public void testHexEncode() throws NoSuchAlgorithmException {
+        MessageDigest sha512 = MessageDigest.getInstance("SHA-512");
+        byte[] hash = sha512.digest(new byte[] {});
+        assertEquals(
+            "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e",
+            MessageDigestChecker.hexEncode(hash));
+    }
+
+    @Test
+    public void testHexDecode() throws NoSuchAlgorithmException {
+        MessageDigest sha512 = MessageDigest.getInstance("SHA-512");
+        byte[] hash = sha512.digest(new byte[] {});
+        assertArrayEquals(
+            hash,
+            MessageDigestChecker.hexDecode("cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"));
+    }
+}
diff --git a/platform/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/VerifyFileTest.java b/platform/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/VerifyFileTest.java
index 9b6da19..7813bc5 100644
--- a/platform/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/VerifyFileTest.java
+++ b/platform/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/VerifyFileTest.java
@@ -25,20 +25,28 @@
 import java.net.MalformedURLException;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.security.CodeSigner;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.cert.Certificate;
+import java.security.cert.TrustAnchor;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import org.netbeans.api.autoupdate.TestUtils;
 import org.netbeans.junit.NbTestCase;
 
+import static org.netbeans.modules.autoupdate.services.Utilities.VERIFICATION_RESULT_COMPARATOR;
+
 public class VerifyFileTest extends NbTestCase {
 
     private static final Logger LOG = Logger.getLogger(VerifyFileTest.class.getName());
 
     private KeyStore ks;
+    private KeyStore validateKs;
 
     public VerifyFileTest(String testName) {
         super(testName);
@@ -48,9 +56,14 @@
     protected void setUp() throws Exception {
         URL urlToKS = TestUtils.class.getResource("data/test-keystore.jks");
         assertNotNull(urlToKS);
+        URL urlToValidateKS = TestUtils.class.getResource("data/test-validate-keystore.jks");
+        assertNotNull(urlToValidateKS);
         File ksFile = org.openide.util.Utilities.toFile(urlToKS.toURI());
         assertTrue(ksFile.exists());
+        File validateKsFile = org.openide.util.Utilities.toFile(urlToValidateKS.toURI());
+        assertTrue(validateKsFile.exists());
         ks = getKeyStore(ksFile, "password");
+        validateKs = getKeyStore(validateKsFile, "password");
     }
 
     private String doVerification(String path) throws URISyntaxException, IOException, KeyStoreException {
@@ -58,9 +71,36 @@
         assertNotNull(urlToFile);
         File jar = org.openide.util.Utilities.toFile(urlToFile.toURI());
         assertTrue(jar.exists());
-        Collection<Certificate> nbmCertificates = Utilities.getNbmCertificates(jar);
-        Collection<Certificate> trustedCertificates = Utilities.getCertificates(ks);
-        return Utilities.verifyCertificates(nbmCertificates, trustedCertificates);
+        String res = null;
+        try {
+            Collection<CodeSigner> nbmCerts = Utilities.getNbmCertificates(jar);
+            Collection<Certificate> trustedCerts = Utilities.getCertificates(ks);
+            Set<TrustAnchor> trustedCACerts = Collections.EMPTY_SET;
+            Collection<Certificate> validationAnchors = new HashSet<>(Utilities.getCertificates(validateKs));
+            Set<TrustAnchor> validationCACerts = Collections.EMPTY_SET;
+            if (nbmCerts == null) {
+                res = Utilities.N_A;
+            } else if (nbmCerts.isEmpty()) {
+                res = Utilities.UNSIGNED;
+            } else {
+                // Iterate all certpaths that can be considered for the NBM
+                // choose the certpath, that has the highest trust level
+                // TRUSTED -> SIGNATURE_VERIFIED -> SIGNATURE_UNVERIFIED -> UNSIGNED
+                // or comes first
+                for (CodeSigner cp : nbmCerts) {
+                    String localRes = Utilities.verifyCertificates(cp, trustedCerts, trustedCACerts, validationAnchors, validationCACerts);
+                    if (res == null
+                        || VERIFICATION_RESULT_COMPARATOR.compare(res, localRes) > 0) {
+                        res = localRes;
+                    }
+                }
+            }
+        } catch (SecurityException ex) {
+            LOG.log(Level.INFO, "The content of the jar/nbm has been modified or certificate paths were inconsistent - " + ex.getMessage(), ex);
+            res = Utilities.MODIFIED;
+        }
+
+        return res;
     }
 
     @SuppressWarnings("unchecked")
@@ -86,6 +126,18 @@
         assertEquals(Utilities.TRUSTED, doVerification("data/dummy-signed-twice.jar"));
     }
 
+    public void testValidatedSigned() throws MalformedURLException, URISyntaxException, IOException, KeyStoreException {
+        assertEquals(Utilities.SIGNATURE_VERIFIED, doVerification("data/dummy-validated.jar"));
+    }
+
+    public void testUnvalidatedSigned() throws MalformedURLException, URISyntaxException, IOException, KeyStoreException {
+        assertEquals(Utilities.SIGNATURE_UNVERIFIED, doVerification("data/dummy-unvalidated.jar"));
+    }
+
+    public void testUnsignedPartiallySigned() throws MalformedURLException, URISyntaxException, IOException, KeyStoreException {
+        assertEquals(Utilities.MODIFIED, doVerification("data/dummy-partial-signed.jar"));
+    }
+
     private static KeyStore getKeyStore(File file, String password) {
         if (file == null) {
             return null;
diff --git a/platform/autoupdate.ui/manifest.mf b/platform/autoupdate.ui/manifest.mf
index 40be84a..d55d1d8 100644
--- a/platform/autoupdate.ui/manifest.mf
+++ b/platform/autoupdate.ui/manifest.mf
@@ -2,6 +2,6 @@
 OpenIDE-Module: org.netbeans.modules.autoupdate.ui
 OpenIDE-Module-Install: org/netbeans/modules/autoupdate/ui/actions/Installer.class
 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/autoupdate/ui/resources/Bundle.properties
-OpenIDE-Module-Specification-Version: 1.52
+OpenIDE-Module-Specification-Version: 1.53
 AutoUpdate-Show-In-Client: false
 AutoUpdate-Essential-Module: true
diff --git a/platform/autoupdate.ui/nbproject/project.xml b/platform/autoupdate.ui/nbproject/project.xml
index 8615fb5..d09a3ce 100644
--- a/platform/autoupdate.ui/nbproject/project.xml
+++ b/platform/autoupdate.ui/nbproject/project.xml
@@ -56,7 +56,7 @@
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <specification-version>1.57</specification-version>
+                        <specification-version>1.62</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
@@ -101,14 +101,6 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.openide.util.ui</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>9.3</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
                     <code-name-base>org.openide.util</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
@@ -125,6 +117,14 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
+                    <code-name-base>org.openide.util.ui</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>9.3</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.openide.windows</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
diff --git a/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/Bundle.properties b/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/Bundle.properties
index af27211..e718266 100644
--- a/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/Bundle.properties
+++ b/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/Bundle.properties
@@ -211,3 +211,4 @@
 Unit_InternalUpdates_Title=Internal Updates
 ProblemPanel.cbShowAgain.text=&Don't show this message again
 SettingsTab.lLocation.text=Plugin &Install Location:
+UpdateUnitProviderPanel.cbTrusted.text=&Trust update center fully and allow automatic installations
diff --git a/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/ProblemPanel.form b/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/ProblemPanel.form
index dce5dfb..bc654d7 100644
--- a/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/ProblemPanel.form
+++ b/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/ProblemPanel.form
@@ -22,6 +22,11 @@
 -->
 
 <Form version="1.4" maxVersion="1.4" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+  <Properties>
+    <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+      <Dimension value="[700, 400]"/>
+    </Property>
+  </Properties>
   <AccessibilityProperties>
     <Property name="AccessibleContext.accessibleDescription" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
       <ResourceString bundle="org/netbeans/modules/autoupdate/ui/wizards/Bundle.properties" key="NetworkProblemPanel_ACD" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
@@ -42,16 +47,16 @@
     <DimensionLayout dim="0">
       <Group type="103" groupAlignment="0" attributes="0">
           <Component id="cbShowAgain" alignment="1" max="32767" attributes="0"/>
-          <Component id="jScrollPane1" alignment="0" pref="425" max="32767" attributes="0"/>
+          <Component id="jScrollPane1" alignment="0" pref="682" max="32767" attributes="0"/>
           <Component id="jScrollPane2" alignment="0" max="32767" attributes="0"/>
       </Group>
     </DimensionLayout>
     <DimensionLayout dim="1">
       <Group type="103" groupAlignment="0" attributes="0">
           <Group type="102" alignment="1" attributes="0">
-              <Component id="jScrollPane2" pref="106" max="32767" attributes="0"/>
+              <Component id="jScrollPane2" pref="140" max="32767" attributes="0"/>
               <EmptySpace min="-2" max="-2" attributes="0"/>
-              <Component id="jScrollPane1" pref="169" max="32767" attributes="0"/>
+              <Component id="jScrollPane1" pref="201" max="32767" attributes="0"/>
               <EmptySpace min="-2" max="-2" attributes="0"/>
               <Component id="cbShowAgain" min="-2" max="-2" attributes="0"/>
           </Group>
@@ -79,11 +84,7 @@
         <Component class="javax.swing.JTextPane" name="tpMessage">
           <Properties>
             <Property name="editable" type="boolean" value="false"/>
-            <Property name="contentType" type="java.lang.String" value="text/html" noResource="true"/>
             <Property name="opaque" type="boolean" value="false"/>
-            <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
-              <Dimension value="[400, 100]"/>
-            </Property>
           </Properties>
         </Component>
       </SubComponents>
@@ -98,11 +99,7 @@
         <Component class="javax.swing.JTextPane" name="taTitle">
           <Properties>
             <Property name="editable" type="boolean" value="false"/>
-            <Property name="contentType" type="java.lang.String" value="text/html" noResource="true"/>
             <Property name="opaque" type="boolean" value="false"/>
-            <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
-              <Dimension value="[200, 100]"/>
-            </Property>
           </Properties>
         </Component>
       </SubComponents>
diff --git a/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/ProblemPanel.java b/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/ProblemPanel.java
index 6876782..cc4ab13 100644
--- a/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/ProblemPanel.java
+++ b/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/ProblemPanel.java
@@ -176,6 +176,8 @@
         jScrollPane2 = new javax.swing.JScrollPane();
         taTitle = new javax.swing.JTextPane();
 
+        setPreferredSize(new java.awt.Dimension(700, 400));
+
         org.openide.awt.Mnemonics.setLocalizedText(cbShowAgain, org.openide.util.NbBundle.getMessage(ProblemPanel.class, "ProblemPanel.cbShowAgain.text")); // NOI18N
         cbShowAgain.addActionListener(new java.awt.event.ActionListener() {
             public void actionPerformed(java.awt.event.ActionEvent evt) {
@@ -184,15 +186,11 @@
         });
 
         tpMessage.setEditable(false);
-        tpMessage.setContentType("text/html"); // NOI18N
         tpMessage.setOpaque(false);
-        tpMessage.setPreferredSize(new java.awt.Dimension(400, 100));
         jScrollPane1.setViewportView(tpMessage);
 
         taTitle.setEditable(false);
-        taTitle.setContentType("text/html"); // NOI18N
         taTitle.setOpaque(false);
-        taTitle.setPreferredSize(new java.awt.Dimension(200, 100));
         jScrollPane2.setViewportView(taTitle);
 
         javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
@@ -200,15 +198,15 @@
         layout.setHorizontalGroup(
             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
             .addComponent(cbShowAgain, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
-            .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 425, Short.MAX_VALUE)
+            .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 682, Short.MAX_VALUE)
             .addComponent(jScrollPane2)
         );
         layout.setVerticalGroup(
             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
             .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
-                .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 106, Short.MAX_VALUE)
+                .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 140, Short.MAX_VALUE)
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 169, Short.MAX_VALUE)
+                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 201, Short.MAX_VALUE)
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                 .addComponent(cbShowAgain))
         );
diff --git a/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/SettingsTab.java b/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/SettingsTab.java
index 40e33dd..49024ee 100644
--- a/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/SettingsTab.java
+++ b/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/SettingsTab.java
@@ -512,6 +512,7 @@
         }
         
         provider.setEnable (panel.isActive ());
+        provider.setTrusted(panel.isTrusted());
         if (panel.isActive ()) {
             // was not enabled and will be -> add it to model and read its content
             refreshProvider (provider, forceRead);
@@ -566,7 +567,8 @@
                 final UpdateUnitProviderPanel panel = new UpdateUnitProviderPanel(provider.isEnabled(),
                         provider.getDisplayName(), // display name
                         provider.getProviderURL().toExternalForm(), // URL
-                        true); // editing
+                        true, // editing
+                        provider.isTrusted());
                 DialogDescriptor descriptor = getCustomizerDescriptor(panel);
                 panel.getOKButton().addActionListener(new ActionListener(){
                     @Override
@@ -628,6 +630,7 @@
             final UpdateUnitProviderPanel panel = new UpdateUnitProviderPanel(true,
                     NbBundle.getMessage(SettingsTab.class, "SettingsTab_NewProviderName"), // NOI18N
                     NbBundle.getMessage(SettingsTab.class, "SettingsTab_NewProviderURL"), // NOI18N
+                    false,
                     false);
             DialogDescriptor descriptor = getCustomizerDescriptor(panel);
             panel.getOKButton().addActionListener(new ActionListener(){
diff --git a/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/UpdateUnitProviderPanel.form b/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/UpdateUnitProviderPanel.form
index bf2d7c3..495a23c 100644
--- a/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/UpdateUnitProviderPanel.form
+++ b/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/UpdateUnitProviderPanel.form
@@ -49,7 +49,13 @@
                       <Group type="103" groupAlignment="0" attributes="0">
                           <Component id="tfURL" alignment="0" pref="474" max="32767" attributes="0"/>
                           <Component id="tfName" pref="474" max="32767" attributes="0"/>
-                          <Component id="cbActive" alignment="0" min="-2" max="-2" attributes="0"/>
+                          <Group type="102" attributes="0">
+                              <Group type="103" groupAlignment="0" attributes="0">
+                                  <Component id="cbTrusted" min="-2" max="-2" attributes="0"/>
+                                  <Component id="cbActive" min="-2" max="-2" attributes="0"/>
+                              </Group>
+                              <EmptySpace min="0" pref="0" max="32767" attributes="0"/>
+                          </Group>
                       </Group>
                   </Group>
               </Group>
@@ -72,7 +78,9 @@
                   <Component id="lURL" alignment="3" min="-2" max="-2" attributes="0"/>
                   <Component id="tfURL" alignment="3" min="-2" max="-2" attributes="0"/>
               </Group>
-              <EmptySpace pref="20" max="32767" attributes="0"/>
+              <EmptySpace type="separate" max="-2" attributes="0"/>
+              <Component id="cbTrusted" min="-2" max="-2" attributes="0"/>
+              <EmptySpace max="32767" attributes="0"/>
               <Component id="errorLabel" min="-2" max="-2" attributes="0"/>
               <EmptySpace max="-2" attributes="0"/>
           </Group>
@@ -142,5 +150,20 @@
     </Component>
     <Component class="javax.swing.JLabel" name="errorLabel">
     </Component>
+    <Component class="javax.swing.JCheckBox" name="cbTrusted">
+      <Properties>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/netbeans/modules/autoupdate/ui/Bundle.properties" key="UpdateUnitProviderPanel.cbTrusted.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+        <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+          <Border info="org.netbeans.modules.form.compat2.border.EmptyBorderInfo">
+            <EmptyBorder bottom="0" left="0" right="0" top="0"/>
+          </Border>
+        </Property>
+      </Properties>
+      <AuxValues>
+        <AuxValue name="generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
+      </AuxValues>
+    </Component>
   </SubComponents>
 </Form>
diff --git a/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/UpdateUnitProviderPanel.java b/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/UpdateUnitProviderPanel.java
index 98e2dc1..c419202 100644
--- a/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/UpdateUnitProviderPanel.java
+++ b/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/UpdateUnitProviderPanel.java
@@ -50,7 +50,7 @@
     private final Icon errorIcon = ImageUtilities.loadImageIcon("org/netbeans/modules/autoupdate/ui/resources/error.png", false);
 
     /** Creates new form UpdateUnitProviderPanel */
-    public UpdateUnitProviderPanel(boolean isActive, String name, String url, boolean editing) {
+    public UpdateUnitProviderPanel(boolean isActive, String name, String url, boolean editing, boolean isTrusted) {
         isEdit = editing;
         originalName = name;
         initComponents();
@@ -58,6 +58,7 @@
         tfURL.setText(url);
         tfName.setText(name);
         cbActive.setSelected(isActive);
+        cbTrusted.setSelected(isTrusted);
         getAccessibleContext().setAccessibleName("ACN_UpdateCenterCustomizer");
         getAccessibleContext().setAccessibleDescription("ACD_UpdateCenterCustomizer");
     }
@@ -239,7 +240,11 @@
     public String getProviderURL() {
         return tfURL.getText().trim();
     }    
-    
+
+    public boolean isTrusted() {
+        return cbTrusted.isSelected();
+    }
+
     /** This method is called from within the constructor to
      * initialize the form.
      * WARNING: Do NOT modify this code. The content of this method is
@@ -254,6 +259,7 @@
         tfURL = new javax.swing.JTextField();
         cbActive = new javax.swing.JCheckBox();
         errorLabel = new javax.swing.JLabel();
+        cbTrusted = new javax.swing.JCheckBox();
 
         lName.setLabelFor(tfName);
         org.openide.awt.Mnemonics.setLocalizedText(lName, org.openide.util.NbBundle.getMessage(UpdateUnitProviderPanel.class, "UpdateUnitProviderPanel.lName.text")); // NOI18N
@@ -264,6 +270,9 @@
         org.openide.awt.Mnemonics.setLocalizedText(cbActive, org.openide.util.NbBundle.getMessage(UpdateUnitProviderPanel.class, "UpdateUnitProviderPanel.cbActive.text")); // NOI18N
         cbActive.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0));
 
+        org.openide.awt.Mnemonics.setLocalizedText(cbTrusted, org.openide.util.NbBundle.getMessage(UpdateUnitProviderPanel.class, "UpdateUnitProviderPanel.cbTrusted.text")); // NOI18N
+        cbTrusted.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0));
+
         javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
         this.setLayout(layout);
         layout.setHorizontalGroup(
@@ -280,7 +289,11 @@
                         .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                             .addComponent(tfURL, javax.swing.GroupLayout.DEFAULT_SIZE, 474, Short.MAX_VALUE)
                             .addComponent(tfName, javax.swing.GroupLayout.DEFAULT_SIZE, 474, Short.MAX_VALUE)
-                            .addComponent(cbActive))))
+                            .addGroup(layout.createSequentialGroup()
+                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                                    .addComponent(cbTrusted)
+                                    .addComponent(cbActive))
+                                .addGap(0, 0, Short.MAX_VALUE)))))
                 .addContainerGap())
         );
         layout.setVerticalGroup(
@@ -296,7 +309,9 @@
                 .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                     .addComponent(lURL)
                     .addComponent(tfURL, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 20, Short.MAX_VALUE)
+                .addGap(18, 18, 18)
+                .addComponent(cbTrusted)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                 .addComponent(errorLabel)
                 .addContainerGap())
         );
@@ -309,6 +324,7 @@
     
     // Variables declaration - do not modify//GEN-BEGIN:variables
     private javax.swing.JCheckBox cbActive;
+    private javax.swing.JCheckBox cbTrusted;
     private javax.swing.JLabel errorLabel;
     private javax.swing.JLabel lName;
     private javax.swing.JLabel lURL;
diff --git a/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/wizards/Bundle.properties b/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/wizards/Bundle.properties
index e0f4999..7086f1f 100644
--- a/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/wizards/Bundle.properties
+++ b/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/wizards/Bundle.properties
@@ -164,4 +164,5 @@
 InstallStep_DownloadProblem_SomePlugins=selected plugins
 LicenseApprovalPanel.lbPlugins.text=Plugins:
 OperationDescriptionStep_NoteCachesNotBuilt=<br><hr><br>\
-    <b>Note:</b> Missing modules may be published on Update Centers. Please run "<b>Check for Newest</b>" from the "<b>Available Plugins</b>" tab.
\ No newline at end of file
+    <b>Note:</b> Missing modules may be published on Update Centers. Please run "<b>Check for Newest</b>" from the "<b>Available Plugins</b>" tab.
+ValidationWarningPanel.taError.text=Error: There were plugins, that were modified or damaged, Installation can't continue.
diff --git a/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/wizards/ValidationWarningPanel.form b/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/wizards/ValidationWarningPanel.form
index 70d3f6c..09ee81d 100644
--- a/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/wizards/ValidationWarningPanel.form
+++ b/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/wizards/ValidationWarningPanel.form
@@ -22,6 +22,20 @@
 -->
 
 <Form version="1.4" maxVersion="1.4" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+  <Properties>
+    <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+      <Border info="org.netbeans.modules.form.compat2.border.EmptyBorderInfo">
+        <EmptyBorder bottom="6" left="6" right="6" top="6"/>
+      </Border>
+    </Property>
+    <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+      <Dimension value="[480, 400]"/>
+    </Property>
+    <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+      <Dimension value="[480, 400]"/>
+    </Property>
+    <Property name="requestFocusEnabled" type="boolean" value="false"/>
+  </Properties>
   <AccessibilityProperties>
     <Property name="AccessibleContext.accessibleDescription" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
       <ResourceString bundle="org/netbeans/modules/autoupdate/ui/wizards/Bundle.properties" key="ValidationWarningPanel_ACD" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
@@ -36,36 +50,10 @@
     <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
     <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
     <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+    <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,4,29,0,0,1,-30"/>
   </AuxValues>
 
-  <Layout>
-    <DimensionLayout dim="0">
-      <Group type="103" groupAlignment="0" attributes="0">
-          <Group type="102" attributes="0">
-              <EmptySpace min="-2" max="-2" attributes="0"/>
-              <Group type="103" groupAlignment="0" attributes="0">
-                  <Component id="spPlugins" alignment="0" max="32767" attributes="0"/>
-                  <Component id="taHead" alignment="0" max="32767" attributes="2"/>
-                  <Component id="taWarning" alignment="1" max="32767" attributes="2"/>
-              </Group>
-              <EmptySpace min="-2" max="-2" attributes="0"/>
-          </Group>
-      </Group>
-    </DimensionLayout>
-    <DimensionLayout dim="1">
-      <Group type="103" groupAlignment="0" attributes="0">
-          <Group type="102" alignment="0" attributes="0">
-              <EmptySpace min="-2" max="-2" attributes="0"/>
-              <Component id="taHead" min="-2" pref="57" max="-2" attributes="0"/>
-              <EmptySpace min="-2" max="-2" attributes="0"/>
-              <Component id="spPlugins" pref="196" max="32767" attributes="0"/>
-              <EmptySpace min="-2" max="-2" attributes="0"/>
-              <Component id="taWarning" pref="58" max="32767" attributes="0"/>
-              <EmptySpace min="-2" max="-2" attributes="0"/>
-          </Group>
-      </Group>
-    </DimensionLayout>
-  </Layout>
+  <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
   <SubComponents>
     <Component class="javax.swing.JTextArea" name="taHead">
       <Properties>
@@ -82,8 +70,37 @@
           <ResourceString bundle="org/netbeans/modules/autoupdate/ui/wizards/Bundle.properties" key="ValidationWarningPanel_taHead_ACN" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
         </Property>
       </AccessibilityProperties>
+      <Constraints>
+        <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+          <GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="3" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
+        </Constraint>
+      </Constraints>
+    </Component>
+    <Component class="javax.swing.JTextArea" name="taError">
+      <Properties>
+        <Property name="editable" type="boolean" value="false"/>
+        <Property name="foreground" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
+          <Color blue="12" green="0" red="cc" type="rgb"/>
+        </Property>
+        <Property name="lineWrap" type="boolean" value="true"/>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/netbeans/modules/autoupdate/ui/wizards/Bundle.properties" key="ValidationWarningPanel.taError.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+        <Property name="wrapStyleWord" type="boolean" value="true"/>
+        <Property name="opaque" type="boolean" value="false"/>
+      </Properties>
+      <Constraints>
+        <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+          <GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="3" insetsRight="0" anchor="512" weightX="0.0" weightY="0.0"/>
+        </Constraint>
+      </Constraints>
     </Component>
     <Container class="javax.swing.JScrollPane" name="spPlugins">
+      <Constraints>
+        <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+          <GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="0" insetsBottom="3" insetsRight="0" anchor="18" weightX="1.0" weightY="1.0"/>
+        </Constraint>
+      </Constraints>
 
       <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
       <SubComponents>
@@ -114,6 +131,11 @@
           <ResourceString bundle="org/netbeans/modules/autoupdate/ui/wizards/Bundle.properties" key="ValidationWarningPanel_taWarning_Text_ACD" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
         </Property>
       </AccessibilityProperties>
+      <Constraints>
+        <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+          <GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="3" insetsLeft="0" insetsBottom="3" insetsRight="0" anchor="512" weightX="0.0" weightY="0.0"/>
+        </Constraint>
+      </Constraints>
     </Component>
   </SubComponents>
 </Form>
diff --git a/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/wizards/ValidationWarningPanel.java b/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/wizards/ValidationWarningPanel.java
index a60b35d..e413163 100644
--- a/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/wizards/ValidationWarningPanel.java
+++ b/platform/autoupdate.ui/src/org/netbeans/modules/autoupdate/ui/wizards/ValidationWarningPanel.java
@@ -44,6 +44,8 @@
         taHead.setBorder(BorderFactory.createEmptyBorder());
         taWarning.setBackground( new Color(0,0,0,0) );
         taWarning.setBorder(BorderFactory.createEmptyBorder());
+        taError.setVisible(! modified.isEmpty());
+        taHead.setVisible(modified.isEmpty());
         postInitComponents (signedVerified, signedUnverified, unsigned, modified, total);
     }
     
@@ -51,6 +53,7 @@
         DefaultMutableTreeNode signedAndValidNode = new DefaultMutableTreeNode("Signed and Valid (" + signedVerified.size() + ")");
         DefaultMutableTreeNode selfSignedNode = new DefaultMutableTreeNode("Self signed (" + signedUnverified.size() + ")");
         DefaultMutableTreeNode unsignedNode = new DefaultMutableTreeNode("Unsigned (" + unsigned.size() + ")");
+        DefaultMutableTreeNode modifiedNode = new DefaultMutableTreeNode("Modified/Damaged (" + modified.size() + ")");
         
         for (UpdateElement el : signedVerified) {
             signedAndValidNode.add(new DefaultMutableTreeNode(el.getDisplayName()));
@@ -66,7 +69,12 @@
             unsignedNode.add(new DefaultMutableTreeNode(el.getDisplayName()));
         } 
         pluginModelRoot.add(unsignedNode);
-        
+
+        for (UpdateElement el : modified) {
+            modifiedNode.add(new DefaultMutableTreeNode(el.getDisplayName()));
+        }
+        pluginModelRoot.add(modifiedNode);
+
         pluginsModel.reload();
         
         int requiresAttention = signedVerified.size() + signedUnverified.size() + unsigned.size();
@@ -78,6 +86,10 @@
             taHead.setText(org.openide.util.NbBundle.getMessage(ValidationWarningPanel.class, "ValidationWarningPanel_taHead_NotTrustedTextSg", 
                     requiresAttention, total));
         }
+
+        if(modified.size() > 0) {
+            
+        }
     }
     
     public String getSelectedNode() {
@@ -96,52 +108,73 @@
      */
     // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
     private void initComponents() {
+        java.awt.GridBagConstraints gridBagConstraints;
 
         taHead = new javax.swing.JTextArea();
+        taError = new javax.swing.JTextArea();
         spPlugins = new javax.swing.JScrollPane();
         tPlugins = new javax.swing.JTree();
         taWarning = new javax.swing.JTextArea();
 
+        setBorder(javax.swing.BorderFactory.createEmptyBorder(6, 6, 6, 6));
+        setMinimumSize(new java.awt.Dimension(480, 400));
+        setPreferredSize(new java.awt.Dimension(480, 400));
+        setRequestFocusEnabled(false);
+        setLayout(new java.awt.GridBagLayout());
+
         taHead.setEditable(false);
         taHead.setLineWrap(true);
         taHead.setWrapStyleWord(true);
         taHead.setOpaque(false);
+        gridBagConstraints = new java.awt.GridBagConstraints();
+        gridBagConstraints.gridx = 0;
+        gridBagConstraints.gridy = 0;
+        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+        gridBagConstraints.insets = new java.awt.Insets(0, 0, 3, 0);
+        add(taHead, gridBagConstraints);
+        taHead.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(ValidationWarningPanel.class, "ValidationWarningPanel_taHead_ACN")); // NOI18N
+        taHead.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(ValidationWarningPanel.class, "ValidationWarningPanel_taHead_ACN")); // NOI18N
+
+        taError.setEditable(false);
+        taError.setForeground(new java.awt.Color(204, 0, 18));
+        taError.setLineWrap(true);
+        taError.setText(org.openide.util.NbBundle.getMessage(ValidationWarningPanel.class, "ValidationWarningPanel.taError.text")); // NOI18N
+        taError.setWrapStyleWord(true);
+        taError.setOpaque(false);
+        gridBagConstraints = new java.awt.GridBagConstraints();
+        gridBagConstraints.gridx = 0;
+        gridBagConstraints.gridy = 1;
+        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+        gridBagConstraints.anchor = java.awt.GridBagConstraints.BASELINE_LEADING;
+        gridBagConstraints.insets = new java.awt.Insets(0, 0, 3, 0);
+        add(taError, gridBagConstraints);
 
         tPlugins.setModel(pluginsModel);
         spPlugins.setViewportView(tPlugins);
 
+        gridBagConstraints = new java.awt.GridBagConstraints();
+        gridBagConstraints.gridx = 0;
+        gridBagConstraints.gridy = 2;
+        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+        gridBagConstraints.weightx = 1.0;
+        gridBagConstraints.weighty = 1.0;
+        gridBagConstraints.insets = new java.awt.Insets(3, 0, 3, 0);
+        add(spPlugins, gridBagConstraints);
+
         taWarning.setEditable(false);
         taWarning.setLineWrap(true);
         taWarning.setText(org.openide.util.NbBundle.getMessage(ValidationWarningPanel.class, "ValidationWarningPanel_taWarning_Text")); // NOI18N
         taWarning.setWrapStyleWord(true);
         taWarning.setOpaque(false);
-
-        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
-        this.setLayout(layout);
-        layout.setHorizontalGroup(
-            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addGroup(layout.createSequentialGroup()
-                .addContainerGap()
-                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-                    .addComponent(spPlugins, javax.swing.GroupLayout.DEFAULT_SIZE, 398, Short.MAX_VALUE)
-                    .addComponent(taHead)
-                    .addComponent(taWarning, javax.swing.GroupLayout.Alignment.TRAILING))
-                .addContainerGap())
-        );
-        layout.setVerticalGroup(
-            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addGroup(layout.createSequentialGroup()
-                .addContainerGap()
-                .addComponent(taHead, javax.swing.GroupLayout.PREFERRED_SIZE, 57, javax.swing.GroupLayout.PREFERRED_SIZE)
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(spPlugins, javax.swing.GroupLayout.DEFAULT_SIZE, 196, Short.MAX_VALUE)
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(taWarning, javax.swing.GroupLayout.PREFERRED_SIZE, 58, Short.MAX_VALUE)
-                .addContainerGap())
-        );
-
-        taHead.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(ValidationWarningPanel.class, "ValidationWarningPanel_taHead_ACN")); // NOI18N
-        taHead.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(ValidationWarningPanel.class, "ValidationWarningPanel_taHead_ACN")); // NOI18N
+        gridBagConstraints = new java.awt.GridBagConstraints();
+        gridBagConstraints.gridx = 0;
+        gridBagConstraints.gridy = 3;
+        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+        gridBagConstraints.anchor = java.awt.GridBagConstraints.BASELINE_LEADING;
+        gridBagConstraints.insets = new java.awt.Insets(3, 0, 3, 0);
+        add(taWarning, gridBagConstraints);
         taWarning.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(ValidationWarningPanel.class, "ValidationWarningPanel_taWarning_Text_ACN")); // NOI18N
         taWarning.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(ValidationWarningPanel.class, "ValidationWarningPanel_taWarning_Text_ACD")); // NOI18N
 
@@ -152,6 +185,7 @@
     // Variables declaration - do not modify//GEN-BEGIN:variables
     private javax.swing.JScrollPane spPlugins;
     private javax.swing.JTree tPlugins;
+    private javax.swing.JTextArea taError;
     private javax.swing.JTextArea taHead;
     private javax.swing.JTextArea taWarning;
     // End of variables declaration//GEN-END:variables