From 4ecfdc7a6cf7a3d8618e9d613505aa4562ab461f Mon Sep 17 00:00:00 2001 From: kohsuke Date: Sun, 25 Oct 2009 16:42:00 +0000 Subject: [PATCH] integrated signature validation code, but off by default until we verify that it's working. git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@23181 71c3de6d-444a-0410-be80-ed276b4c234a --- core/pom.xml | 5 ++ .../main/java/hudson/model/UpdateCenter.java | 73 ++++++++++++++++++- 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/core/pom.xml b/core/pom.xml index e6f3f07810..dc78025fa4 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -298,6 +298,11 @@ THE SOFTWARE. cli ${project.version} + + org.jvnet.hudson + crypto-util + 1.0 + org.jvnet.hudson jtidy diff --git a/core/src/main/java/hudson/model/UpdateCenter.java b/core/src/main/java/hudson/model/UpdateCenter.java index 3d7c2f6e48..1f69e66b88 100644 --- a/core/src/main/java/hudson/model/UpdateCenter.java +++ b/core/src/main/java/hudson/model/UpdateCenter.java @@ -41,9 +41,12 @@ import org.acegisecurity.Authentication; import org.apache.commons.io.input.CountingInputStream; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.NullOutputStream; +import org.apache.commons.io.output.TeeOutputStream; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; +import org.jvnet.hudson.crypto.SignatureOutputStream; +import org.jvnet.hudson.crypto.CertificateUtil; import javax.servlet.ServletException; import javax.net.ssl.SSLHandshakeException; @@ -52,6 +55,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.InputStream; +import java.io.ByteArrayInputStream; +import java.io.OutputStreamWriter; import java.net.URL; import java.net.URLConnection; import java.net.UnknownHostException; @@ -70,6 +75,14 @@ import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; +import java.security.MessageDigest; +import java.security.DigestOutputStream; +import java.security.GeneralSecurityException; +import java.security.Signature; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +import com.trilead.ssh2.crypto.Base64; /** * Controls update center capability. @@ -181,7 +194,7 @@ public class UpdateCenter extends AbstractModelObject { /** * This is the endpoint that receives the update center data file from the browser. */ - public void doPostBack(StaplerRequest req) throws IOException { + public void doPostBack(StaplerRequest req) throws IOException, GeneralSecurityException { dataTimestamp = System.currentTimeMillis(); String p = req.getParameter("json"); JSONObject o = JSONObject.fromObject(p); @@ -192,10 +205,63 @@ public class UpdateCenter extends AbstractModelObject { return; } + if (signatureCheck) + verifySignature(o); + LOGGER.info("Obtained the latest update center data file"); getDataFile().write(p); } + /** + * Verifies the signature in the update center data file. + */ + private boolean verifySignature(JSONObject o) throws GeneralSecurityException, IOException { + JSONObject signature = o.getJSONObject("signature"); + if (signature.isNullObject()) { + LOGGER.severe("No signature block found"); + return false; + } + o.remove("signature"); + + List certs = new ArrayList(); + {// load and verify certificates + CertificateFactory cf = CertificateFactory.getInstance("X509"); + for (Object cert : o.getJSONArray("certificates")) { + X509Certificate c = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(Base64.decode(cert.toString().toCharArray()))); + c.checkValidity(); + certs.add(c); + } + CertificateUtil.validatePath(certs); + } + + // this is for computing a digest to check sanity + MessageDigest sha1 = MessageDigest.getInstance("SHA1"); + DigestOutputStream dos = new DigestOutputStream(new NullOutputStream(),sha1); + + // this is for computing a signature + Signature sig = Signature.getInstance("SHA1withRSA"); + sig.initVerify(certs.get(0)); + SignatureOutputStream sos = new SignatureOutputStream(sig); + + o.writeCanonical(new OutputStreamWriter(new TeeOutputStream(dos,sos),"UTF-8")); + + // did the digest match? this is not a part of the signature validation, but if we have a bug in the c14n + // (which is more likely than someone tampering with update center), we can tell + String computedDigest = new String(Base64.encode(sha1.digest())); + String providedDigest = signature.getString("digest"); + if (!computedDigest.equalsIgnoreCase(providedDigest)) { + LOGGER.severe("Digest mismatch: "+computedDigest+" vs "+providedDigest); + return false; + } + + if (!sig.verify(Base64.decode(signature.getString("signature").toCharArray()))) { + LOGGER.severe("Signature in the update center doesn't match with the certificate"); + return false; + } + + return true; + } + /** * Schedules a Hudson upgrade. */ @@ -1016,4 +1082,9 @@ public class UpdateCenter extends AbstractModelObject { private static final Logger LOGGER = Logger.getLogger(UpdateCenter.class.getName()); public static boolean neverUpdate = Boolean.getBoolean(UpdateCenter.class.getName()+".never"); + + /** + * Off by default until we know this is reasonably working. + */ + public static boolean signatureCheck = Boolean.getBoolean(UpdateCenter.class.getName()+".signatureCheck"); } -- GitLab