File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\jenkins\util\JSONSignatureValidator.java.html

1 package jenkins.util;
2
3 import com.trilead.ssh2.crypto.Base64;
4 import hudson.util.FormValidation;
5 import jenkins.model.Jenkins;
6 import net.sf.json.JSONObject;
7 import org.apache.commons.io.output.NullOutputStream;
8 import org.apache.commons.io.output.TeeOutputStream;
9 import org.jvnet.hudson.crypto.CertificateUtil;
10 import org.jvnet.hudson.crypto.SignatureOutputStream;
11
12 import java.io.ByteArrayInputStream;
13 import java.io.File;
14 import java.io.FileInputStream;
15 import java.io.IOException;
16 import java.io.OutputStreamWriter;
17 import java.security.DigestOutputStream;
18 import java.security.GeneralSecurityException;
19 import java.security.MessageDigest;
20 import java.security.Signature;
21 import java.security.cert.CertificateExpiredException;
22 import java.security.cert.CertificateFactory;
23 import java.security.cert.CertificateNotYetValidException;
24 import java.security.cert.TrustAnchor;
25 import java.security.cert.X509Certificate;
26 import java.util.ArrayList;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Set;
30
31 /**
32 * @author Kohsuke Kawaguchi
33 */
34 public class JSONSignatureValidator {
35 private final String name;
36
37 public JSONSignatureValidator(String name) {
38 this.name = name;
39 }
40
41 /**
42 * Verifies the signature in the update center data file.
43 */
44 public FormValidation verifySignature(JSONObject o) throws IOException {
45 try {
46 FormValidation warning = null;
47
48 JSONObject signature = o.getJSONObject("signature");
49 if (signature.isNullObject()) {
50 return FormValidation.error("No signature block found in "+name);
51 }
52 o.remove("signature");
53
54 List<X509Certificate> certs = new ArrayList<X509Certificate>();
55 {// load and verify certificates
56 CertificateFactory cf = CertificateFactory.getInstance("X509");
57 for (Object cert : signature.getJSONArray("certificates")) {
58 X509Certificate c = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(Base64.decode(cert.toString().toCharArray())));
59 try {
60 c.checkValidity();
61 } catch (CertificateExpiredException e) { // even if the certificate isn't valid yet, we'll proceed it anyway
62 warning = FormValidation.warning(e,String.format("Certificate %s has expired in %s",cert.toString(),name));
63 } catch (CertificateNotYetValidException e) {
64 warning = FormValidation.warning(e,String.format("Certificate %s is not yet valid in %s",cert.toString(),name));
65 }
66 certs.add(c);
67 }
68
69 // if we trust default root CAs, we end up trusting anyone who has a valid certificate,
70 // which isn't useful at all
71 Set<TrustAnchor> anchors = new HashSet<TrustAnchor>(); // CertificateUtil.getDefaultRootCAs();
72 Jenkins j = Jenkins.getInstance();
73 for (String cert : (Set<String>) j.servletContext.getResourcePaths("/WEB-INF/update-center-rootCAs")) {
74 if (cert.endsWith(".txt")) continue; // skip text files that are meant to be documentation
75 anchors.add(new TrustAnchor((X509Certificate)cf.generateCertificate(j.servletContext.getResourceAsStream(cert)),null));
76 }
77 File[] cas = new File(j.root, "update-center-rootCAs").listFiles();
78 if (cas!=null) {
79 for (File cert : cas) {
80 if (cert.getName().endsWith(".txt")) continue; // skip text files that are meant to be documentation
81 FileInputStream in = new FileInputStream(cert);
82 try {
83 anchors.add(new TrustAnchor((X509Certificate)cf.generateCertificate(in),null));
84 } finally {
85 in.close();
86 }
87 }
88 }
89 CertificateUtil.validatePath(certs, anchors);
90 }
91
92 // this is for computing a digest to check sanity
93 MessageDigest sha1 = MessageDigest.getInstance("SHA1");
94 DigestOutputStream dos = new DigestOutputStream(new NullOutputStream(),sha1);
95
96 // this is for computing a signature
97 Signature sig = Signature.getInstance("SHA1withRSA");
98 sig.initVerify(certs.get(0));
99 SignatureOutputStream sos = new SignatureOutputStream(sig);
100
101 // until JENKINS-11110 fix, UC used to serve invalid digest (and therefore unverifiable signature)
102 // that only covers the earlier portion of the file. This was caused by the lack of close() call
103 // in the canonical writing, which apparently leave some bytes somewhere that's not flushed to
104 // the digest output stream. This affects Jenkins [1.424,1,431].
105 // Jenkins 1.432 shipped with the "fix" (1eb0c64abb3794edce29cbb1de50c93fa03a8229) that made it
106 // compute the correct digest, but it breaks all the existing UC json metadata out there. We then
107 // quickly discovered ourselves in the catch-22 situation. If we generate UC with the correct signature,
108 // it'll cut off [1.424,1.431] from the UC. But if we don't, we'll cut off [1.432,*).
109 //
110 // In 1.433, we revisited 1eb0c64abb3794edce29cbb1de50c93fa03a8229 so that the original "digest"/"signature"
111 // pair continues to be generated in a buggy form, while "correct_digest"/"correct_signature" are generated
112 // correctly.
113 //
114 // Jenkins should ignore "digest"/"signature" pair. Accepting it creates a vulnerability that allows
115 // the attacker to inject a fragment at the end of the json.
116 o.writeCanonical(new OutputStreamWriter(new TeeOutputStream(dos,sos),"UTF-8")).close();
117
118 // did the digest match? this is not a part of the signature validation, but if we have a bug in the c14n
119 // (which is more likely than someone tampering with update center), we can tell
120 String computedDigest = new String(Base64.encode(sha1.digest()));
121 String providedDigest = signature.optString("correct_digest");
122 if (providedDigest==null) {
123 return FormValidation.error("No correct_digest parameter in "+name+". This metadata appears to be old.");
124 }
125 if (!computedDigest.equalsIgnoreCase(providedDigest)) {
126 return FormValidation.error("Digest mismatch: "+computedDigest+" vs "+providedDigest+" in "+name);
127 }
128
129 String providedSignature = signature.getString("correct_signature");
130 if (!sig.verify(Base64.decode(providedSignature.toCharArray()))) {
131 return FormValidation.error("Signature in the update center doesn't match with the certificate in "+name);
132 }
133
134 if (warning!=null) return warning;
135 return FormValidation.ok();
136 } catch (GeneralSecurityException e) {
137 return FormValidation.error(e,"Signature verification failed in "+name);
138 }
139 }
140 }
141