Hi,

Regarding notSignedByAlias warning and exit code (32), it looks like
these don't occur if the specified alias for verifying a jar points to
a keystore entry that signs (or certifies) one of the certificates in
the jar's signature attached certificate chain. That may be useful to
check if a signature and its certification path can be verified the
usual way but in order to verify exactly who originally signed a jar it
may be confusing, misleading, or maybe possibly even wrong.

I would have guessed from the warning "This JAR contains signed entries
which aren't signed by the specified alias(es)." or more specifically
from its absence that the keystore entry the specified alias points to
actually has signed the verified jar, which I believe to have found is
not currently always the case. The warning and exit code don't occur,
too, if one certificate of the certificate chain included in the jar
signature matches the keystore entry the specified alias points to.

As an example, lets consider an example with a key pair ca that acts as
a certificate authority and another key pair b which has a certificate
of ca, see attached
VerifySignedBySpecifiedAliasWithoutOtherAliasesInCertChain.
When a jar is signed with b, jarsigner -verify will accept the
resulting jar as signed not only by b itself but also by ca, or the
notSignedByAlias warning and exit code should have occurred.
If I imagine the ca in this example to be a real certificate authority
such as Geotrust or Verisign and b a certificate for my own key pair
signed or certified by such a real ca, I could make jarsigner act on a
jar as though it had been signed by these certificate authorities
themselves.

For verifying authenticity of a signed jar meaning that it has been
signed by the entity referred to by the specified alias, I figure that
it is not enough to verify that the key pair used to sign the jar has a
certificate chain that includes a certificate that matches the keystore
entry specified by the given alias. I figure it is also necessary to
only compare the one certificate from the signed jar signature that
directly corresponds to the private key used to sign the jar.


The specification contains statements related to jarsigner,
https://docs.oracle.com/en/java/javase/13/docs/specs/man/jarsigner.html
:

> A digital signature is a string of bits that is computed from some
data (the data being signed) and the private key of an entity (a
person, company, and so on). ...
> * Its authenticity can be verified by a computation that uses the
public key corresponding to the private key used to generate the
signature.

> The jarsigner command uses an entity's private key to generate a
signature. The signed JAR file contains, among other things, a copy of
the certificate from the keystore for the public key corresponding to
the private key used to sign the file. The jarsigner command can verify
the digital signature of the signed JAR file using the certificate
inside it (in its signature block file).

> This file also contains, encoded inside it, the certificate or
certificate chain from the keystore that authenticates the public key
corresponding to the private key used for signing.

> ... and in parentheses, the keystore alias for the signer when the
public key certificate in the JAR file matches the one in a keystore
entry ...

Public and private key pairs are mentioned a number of times,
explaining in general how a private key is involved in signing and its
corresponding public key certificate in verifying the resulting
signature. The exact behavior of "jarsigner -verify" is not explicitly
defined, which in my opinion is fine as it should be deducible from the
quoted statements.


The specification contains statements related to keytool,
https://docs.oracle.com/en/java/javase/13/docs/specs/man/keytool.html
:

> The keytool command can create and manage keystore key entries that
each contain a private key and an associated certificate chain. The
first certificate in the chain contains the public key that corresponds
to the private key.

This above, hence, is how to get the public key certificate that
directly corresponds to a private key.


I haven't found such a bug already existing. The closest I found are:
- https://bugs.openjdk.java.net/browse/JDK-7004168
- https://bugs.openjdk.java.net/browse/JDK-4306329


Attached is a patch for jarsigner that shows an approach to fix it.
However, I think it is still incomplete. In order to be complete, I
think it should also check that "jarsigner -verify" works well if more
than one aliases are specified for the same invocation. And similar for
"not in keystore". Furthermore, I changed existing ConciseJarsigner
test to let it continue passing where I believe an exit code has been
put from previous results incompletely verified, which should be
confirmed.


The current behavior can be used to check if a valid certification path
can be found from the signed content and any of its certificate chain
certificates to a keystore entry referred to by a specified alias which
looks useful. It is only confusing that the specification or
documentation does not state it explicitly. At the moment I would not
know how to invoke "jarsigner -verify" in a way to check if some
content has been signed by a specific entity referred to by a given
alias disregarding the certification path of the signature (as in the
keytool's statement "The first certificate in the chain contains the
public key that corresponds to the private key").

Another attempt to solve the confusion could be to change the following
sentence, which would not require to change any code at all:
-This JAR contains signed entries which aren't signed by the specified
alias(es)
+This JAR contains signed entries the certificate path of which aren't
signed by the specified alias(es)

Opinions?

Regards,
Philipp
diff -r 5573a7098439 src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java
--- a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java	Sat Nov 02 10:02:18 2019 +0000
+++ b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java	Sun Nov 03 20:14:55 2019 +0100
@@ -1440,6 +1440,16 @@
             try {
                 List<? extends Certificate> certs =
                         signer.getSignerCertPath().getCertificates();
+                if (!certs.isEmpty()) {
+                    Certificate signerCert = certs.get(0);
+                    for (String ckalias : ckaliases) {
+                        Certificate storeCert = store.getCertificate(ckalias);
+                        if (signerCert.equals(storeCert)) {
+                            result |= SIGNED_BY_ALIAS;
+                        }
+                        break;
+                    }
+                }
                 for (Certificate c : certs) {
                     String alias = storeHash.get(c);
                     if (alias == null) {
@@ -1451,15 +1461,8 @@
                     if (alias != null) {
                         result |= IN_KEYSTORE;
                     }
-                    for (String ckalias : ckaliases) {
-                        if (c.equals(store.getCertificate(ckalias))) {
-                            result |= SIGNED_BY_ALIAS;
-                            // must continue with next certificate c and cannot
-                            // return or break outer loop because has to fill
-                            // storeHash for printCert
-                            break;
-                        }
-                    }
+                    // continue with all certificates c to fill
+                    // storeHash for printCert
                 }
             } catch (KeyStoreException kse) {
                 // never happens, because keystore has been loaded
diff -r 5573a7098439 test/jdk/sun/security/tools/jarsigner/VerifySignedBySpecifiedAliasWithoutOtherAliasesInCertChain.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/sun/security/tools/jarsigner/VerifySignedBySpecifiedAliasWithoutOtherAliasesInCertChain.java	Sun Nov 03 20:14:55 2019 +0100
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import jdk.test.lib.util.JarUtils;
+
+import static org.testng.Assert.*;
+import static jdk.test.lib.SecurityTools.*;
+
+/**
+ * @test
+ * @library /test/lib ./warnings
+ * @run testng VerifySignedBySpecifiedAliasWithoutOtherAliasesInCertChain
+ * @summary Checks that jarsigner verifies whether a JAR file is signed by the
+ * specified alias comparing the the certificate of the public key corresponding
+ * to the private key used to sign (only and disregarding the certificate chain
+ * in the JAR file) to the certificate the specified alias points to.
+ */
+public class VerifySignedBySpecifiedAliasWithoutOtherAliasesInCertChain {
+
+    @org.testng.annotations.Test
+    public void test() throws Exception {
+        // given key pairs ca and b in the same keystore, b signed by ca
+        String KS = "-keystore ks.jks -storepass changeit";
+        keytool(KS + " -genkeypair -alias ca -dname CN=CA");
+        keytool(KS + " -genkeypair -alias b -dname CN=B");
+
+        // request a certificate for b from a, let a sign it, and import it
+        String certReqB = keytool(KS + " -certreq -alias b").getStdout();
+        setResponse(certReqB);
+        String certB = keytool(KS + " -gencert -rfc -alias ca").getStdout();
+        setResponse(certB);
+        keytool(KS + " -importcert -alias b");
+
+        // when signing a jar with b
+        String jarFilename = "test.jar";
+        JarUtils.createJarFile(Paths.get(jarFilename), Paths.get("."),
+                Files.write(Paths.get("file"), new byte[] {'x'}));
+        jarsigner(KS + " -keypass changeit " + jarFilename + " b");
+        jarsigner(KS + " -verify -certs -strict " + jarFilename + " b")
+                .shouldHaveExitValue(0)
+                .shouldNotContain(Test.NOT_SIGNED_BY_ALIAS_VERIFYING_WARNING);
+
+        // then expect the jar not to be signed by ca
+        jarsigner(KS + " -verify -certs -strict " + jarFilename + " ca")
+                .shouldHaveExitValue(Test.NOT_SIGNED_BY_ALIAS_EXIT_CODE)
+                .shouldContain(Test.NOT_SIGNED_BY_ALIAS_VERIFYING_WARNING);
+    }
+
+}
diff -r 5573a7098439 test/jdk/sun/security/tools/jarsigner/warnings/NotSignedByAliasTest.java
--- a/test/jdk/sun/security/tools/jarsigner/warnings/NotSignedByAliasTest.java	Sat Nov 02 10:02:18 2019 +0000
+++ b/test/jdk/sun/security/tools/jarsigner/warnings/NotSignedByAliasTest.java	Sun Nov 03 20:14:55 2019 +0100
@@ -57,7 +57,7 @@
                 FIRST_KEY_ALIAS,
                 "-validity", Integer.toString(VALIDITY));
 
-        // create first key pair for signing
+        // create second key pair for signing
         createAlias(SECOND_KEY_ALIAS);
         issueCert(
                 SECOND_KEY_ALIAS,

Reply via email to