This is an automated email from the ASF dual-hosted git repository.

matthiasblaesing pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git

commit 58ded7130bee8c57c84c12457fb34badcd468939
Author: Matthias Bläsing <[email protected]>
AuthorDate: Sun Nov 3 15:47:07 2019 +0100

    Base certificate validation in UpdateCenter on KeyStore and not on trust to 
the embedded certpath
    
    Instead of trusting the provided chain embedded in a NBM, validity of a
    signature is now checked against a TrustStore, as is already done to
    establish trust.
    
    The KeyStoreProvider SPI was extended to allow to specify a level
    of trust. This way a module can provide trusted and/or validation
    certificates.
---
 platform/autoupdate.services/apichanges.xml        |  23 ++
 platform/autoupdate.services/build.xml             |  28 +++
 platform/autoupdate.services/manifest.mf           |   2 +-
 .../autoupdate/services/InstallSupportImpl.java    |  26 ++-
 .../modules/autoupdate/services/Utilities.java     | 236 +++++++++++----------
 .../netbeans/spi/autoupdate/KeyStoreProvider.java  |  45 +++-
 .../autoupdate/services/VerifyFileTest.java        |  29 ++-
 7 files changed, 262 insertions(+), 127 deletions(-)

diff --git a/platform/autoupdate.services/apichanges.xml 
b/platform/autoupdate.services/apichanges.xml
index 9cee0f7..350a3a2 100644
--- a/platform/autoupdate.services/apichanges.xml
+++ b/platform/autoupdate.services/apichanges.xml
@@ -33,6 +33,29 @@
     <!-- ACTUAL CHANGES BEGIN HERE: -->
 
     <changes>
+        <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 971c989..33f8799 100644
--- a/platform/autoupdate.services/build.xml
+++ b/platform/autoupdate.services/build.xml
@@ -52,6 +52,20 @@
     <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">
@@ -83,6 +97,20 @@
              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/manifest.mf 
b/platform/autoupdate.services/manifest.mf
index 0948f17..f664430 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.61
 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/modules/autoupdate/services/InstallSupportImpl.java
 
b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/InstallSupportImpl.java
index 43e7f84..5db31b9 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,10 +28,13 @@ import java.net.SocketTimeoutException;
 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.CertPath;
 import java.security.cert.Certificate;
+import java.security.cert.TrustAnchor;
+import java.security.cert.X509Certificate;
 import java.util.*;
 import java.util.concurrent.*;
 import java.util.concurrent.atomic.AtomicLong;
@@ -52,6 +55,7 @@ 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;
@@ -1056,9 +1060,21 @@ public class InstallSupportImpl {
         try {
             // get trusted certificates
             Set<Certificate> trustedCerts = new HashSet<> ();
-            for (KeyStore ks : Utilities.getKeyStore ()) {
+            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) {
@@ -1073,7 +1089,7 @@ public class InstallSupportImpl {
             UpdateElementImpl impl = Trampoline.API.impl(el);
 
             try {
-                Collection<CertPath> nbmCerts = 
Utilities.getNbmCertificates(nbmFile);
+                Collection<CodeSigner> nbmCerts = 
Utilities.getNbmCertificates(nbmFile);
                 if(nbmCerts == null) {
                     res = Utilities.N_A;
                 } else if (nbmCerts.isEmpty()) {
@@ -1083,15 +1099,15 @@ public class InstallSupportImpl {
                     // choose the certpath, that has the highest trust level
                     // TRUSTED -> SIGNATURE_VERIFIED -> SIGNATURE_UNVERIFIED
                     // or comes first
-                    for(CertPath cp: nbmCerts) {
-                        String localRes = Utilities.verifyCertificates(cp, 
trustedCerts);
+                    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>) 
cp.getCertificates());
+                            certs.put(el, (List<Certificate>) 
cs.getSignerCertPath().getCertificates());
                         }
                     }
                 }
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 fcd3139..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
@@ -27,16 +27,13 @@ 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;
@@ -60,6 +57,7 @@ import 
org.netbeans.modules.autoupdate.updateprovider.DummyModuleInfo;
 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;
@@ -104,28 +102,38 @@ public class Utilities {
     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 ();
+    public static Collection<KeyStore> getKeyStore (TrustLevel trustLevel) {
+        if (keyStoreLookupResult == null) {
+            keyStoreLookupResult = 
Lookup.getDefault().lookupResult(KeyStoreProvider.class);
+            keyStoreLookupResult.addLookupListener(new 
KeyStoreProviderListener());
         }
 
-       List<KeyStore> kss = new ArrayList<>();
-        for (KeyStoreProvider provider : c) {
-            KeyStore ks = provider.getKeyStore ();
-            if (ks != null) {
-                kss.add (ks);
+        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 kss;
+        return result;
     }
 
     /**
@@ -134,103 +142,80 @@ public class Utilities {
      * @param trustedCertificates
      * @return
      */
-    public static String verifyCertificates(CertPath archiveCertPath, 
Collection<? extends Certificate> trustedCertificates) {
+    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.getCertificates();
-        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);
+        List<? extends Certificate> archiveCertificates = 
archiveCertPath.getSignerCertPath().getCertificates();
 
-                    Principal tap = cert.getIssuerDN();
-                    if (tap != null) {
-                        X509Certificate tempTrustAnchor = 
certSubjectsMap.get(tap);
-                        if (tempTrustAnchor != null) {
-                            candidates.put(cert, tempTrustAnchor);
-                        }
-                    }
-                }
+        if(archiveCertificates.isEmpty()) {
+            return UNSIGNED;
+        }
 
-                // 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;
-                    }
+        // Case 1: We have direct trust into one of the certificates of the
+        //         certificate chain
+        if(isChainTrusted(archiveCertificates, trustedCertificates)) {
+            return TRUSTED;
+        }
 
-                    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;
-                        }
-                    }
-                }
+        // Case 2: We trust the CA, that issued a certificate - do the
+        //         normal CertPathValidation
+        if(verifyCACertificatePath(trustedCACertificates, archiveCertPath)) {
+            return TRUSTED;
+        }
 
-                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;
+        // 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 UNSIGNED;
+        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();
     }
 
     /**
@@ -270,7 +255,7 @@ public class Utilities {
     }
 
     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) {
@@ -281,18 +266,37 @@ public class Utilities {
         return certs;
     }
 
+    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));
+                    }
+                }
+            }
+            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 CertPaths, that were used to sign the 
non-signature
+     * @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<CertPath> getNbmCertificates (File nbmFile) 
throws IOException, SecurityException {
-        Set<CertPath> certs = null;
+    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
@@ -317,11 +321,11 @@ public class Utilities {
                     if(! entry.getName().equals("META-INF/MANIFEST.MF")) {
                         empty = false;
                     }
-                    Set<CertPath> entryCerts = new HashSet<>();
+                    Set<CodeSigner> entryCerts = new HashSet<>();
                     CodeSigner[] codeSigners = entry.getCodeSigners();
                     if (codeSigners != null) {
                         for (CodeSigner cs : entry.getCodeSigners()) {
-                            entryCerts.add(cs.getSignerCertPath());
+                            entryCerts.add(cs);
                         }
                     }
                     if(certs == null) {
@@ -362,7 +366,7 @@ public class Utilities {
         
         @Override
         public void resultChanged (LookupEvent ev) {
-            result = null;
+            keystoreCache.clear();
         }
     }
     
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 @@ import java.security.KeyStore;
  * @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/VerifyFileTest.java
 
b/platform/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/VerifyFileTest.java
index b55e48f..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,11 +25,15 @@ import java.io.InputStream;
 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.CertPath;
 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;
@@ -42,6 +46,7 @@ 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);
@@ -51,9 +56,14 @@ public class VerifyFileTest extends NbTestCase {
     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 {
@@ -63,8 +73,11 @@ public class VerifyFileTest extends NbTestCase {
         assertTrue(jar.exists());
         String res = null;
         try {
-            Collection<CertPath> nbmCerts = Utilities.getNbmCertificates(jar);
+            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()) {
@@ -74,8 +87,8 @@ public class VerifyFileTest extends NbTestCase {
                 // choose the certpath, that has the highest trust level
                 // TRUSTED -> SIGNATURE_VERIFIED -> SIGNATURE_UNVERIFIED -> 
UNSIGNED
                 // or comes first
-                for (CertPath cp : nbmCerts) {
-                    String localRes = Utilities.verifyCertificates(cp, 
trustedCerts);
+                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;
@@ -113,6 +126,14 @@ public class VerifyFileTest extends NbTestCase {
         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"));
     }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists

Reply via email to