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

1 package hudson.util;
2
3 import com.trilead.ssh2.crypto.Base64;
4 import hudson.model.TaskListener;
5 import org.apache.commons.io.FileUtils;
6
7 import javax.crypto.Cipher;
8 import javax.crypto.SecretKey;
9 import java.io.BufferedReader;
10 import java.io.BufferedWriter;
11 import java.io.File;
12 import java.io.FileInputStream;
13 import java.io.IOException;
14 import java.io.InputStreamReader;
15 import java.io.PrintWriter;
16 import java.security.GeneralSecurityException;
17 import java.security.InvalidKeyException;
18 import java.util.HashSet;
19 import java.util.Set;
20
21 /**
22 * Rewrites XML files by looking for Secrets that are stored with the old key and replaces them
23 * by the new encrypted values.
24 *
25 * @author Kohsuke Kawaguchi
26 */
27 public class SecretRewriter {
28 private final Cipher cipher;
29 private final SecretKey key;
30
31 /**
32 * How many files have been scanned?
33 */
34 private int count;
35
36 /**
37 * If non-null the original file before rewrite gets in here.
38 */
39 private final File backupDirectory;
40
41 /**
42 * Canonical paths of the directories we are recursing to protect
43 * against symlink induced cycles.
44 */
45 private Set<String> callstack = new HashSet<String>();
46
47 public SecretRewriter(File backupDirectory) throws GeneralSecurityException {
48 cipher = Secret.getCipher("AES");
49 key = Secret.getLegacyKey();
50 this.backupDirectory = backupDirectory;
51 }
52
53 private String tryRewrite(String s) throws IOException, InvalidKeyException {
54 if (s.length()<24)
55 return s; // Encrypting "" in Secret produces 24-letter characters, so this must be the minimum length
56 if (!isBase64(s))
57 return s; // decode throws IOException if the input is not base64, and this is also a very quick way to filter
58
59 byte[] in;
60 try {
61 in = Base64.decode(s.toCharArray());
62 } catch (IOException e) {
63 return s; // not a valid base64
64 }
65 cipher.init(Cipher.DECRYPT_MODE, key);
66 Secret sec = Secret.tryDecrypt(cipher, in);
67 if(sec!=null) // matched
68 return sec.getEncryptedValue(); // replace by the new encrypted value
69 else // not encrypted with the legacy key. leave it unmodified
70 return s;
71 }
72
73 /**
74 * @param backup
75 * if non-null, the original file will be copied here before rewriting.
76 * if the rewrite doesn't happen, no copying.
77 */
78 public boolean rewrite(File f, File backup) throws InvalidKeyException, IOException {
79 AtomicFileWriter w = new AtomicFileWriter(f, "UTF-8");
80 try {
81 PrintWriter out = new PrintWriter(new BufferedWriter(w));
82
83 boolean modified = false; // did we actually change anything?
84 try {
85 FileInputStream fin = new FileInputStream(f);
86 try {
87 BufferedReader r = new BufferedReader(new InputStreamReader(fin, "UTF-8"));
88 String line;
89 StringBuilder buf = new StringBuilder();
90
91 while ((line=r.readLine())!=null) {
92 int copied=0;
93 buf.setLength(0);
94 while (true) {
95 int sidx = line.indexOf('>',copied);
96 if (sidx<0) break;
97 int eidx = line.indexOf('<',sidx);
98 if (eidx<0) break;
99
100 String elementText = line.substring(sidx+1,eidx);
101 String replacement = tryRewrite(elementText);
102 if (!replacement.equals(elementText))
103 modified = true;
104
105 buf.append(line.substring(copied,sidx+1));
106 buf.append(replacement);
107 copied = eidx;
108 }
109 buf.append(line.substring(copied));
110 out.println(buf.toString());
111 }
112 } finally {
113 fin.close();
114 }
115 } finally {
116 out.close();
117 }
118
119 if (modified) {
120 if (backup!=null) {
121 backup.getParentFile().mkdirs();
122 FileUtils.copyFile(f,backup);
123 }
124 w.commit();
125 }
126 return modified;
127 } finally {
128 w.abort();
129 }
130 }
131
132
133 /**
134 * Recursively scans and rewrites a directory.
135 *
136 * This method shouldn't abort just because one file fails to rewrite.
137 *
138 * @return
139 * Number of files that were actually rewritten.
140 */
141 // synchronized to prevent accidental concurrent use. this instance is not thread safe
142 public synchronized int rewriteRecursive(File dir, TaskListener listener) throws InvalidKeyException {
143 return rewriteRecursive(dir,"",listener);
144 }
145 private int rewriteRecursive(File dir, String relative, TaskListener listener) throws InvalidKeyException {
146 String canonical;
147 try {
148 canonical = dir.getCanonicalPath();
149 } catch (IOException e) {
150 canonical = dir.getAbsolutePath(); //
151 }
152 if (!callstack.add(canonical)) {
153 listener.getLogger().println("Cycle detected: "+dir);
154 return 0;
155 }
156
157 try {
158 File[] children = dir.listFiles();
159 if (children==null) return 0;
160
161 int rewritten=0;
162 for (File child : children) {
163 String cn = child.getName();
164 if (cn.endsWith(".xml")) {
165 if ((count++)%100==0)
166 listener.getLogger().println("Scanning "+child);
167 try {
168 File backup = null;
169 if (backupDirectory!=null) backup = new File(backupDirectory,relative+'/'+ cn);
170 if (rewrite(child,backup)) {
171 if (backup!=null)
172 listener.getLogger().println("Copied "+child+" to "+backup+" as a backup");
173 listener.getLogger().println("Rewritten "+child);
174 rewritten++;
175 }
176 } catch (IOException e) {
177 e.printStackTrace(listener.error("Failed to rewrite "+child));
178 }
179 }
180 if (child.isDirectory()) {
181 if (!isIgnoredDir(child))
182 rewritten += rewriteRecursive(child,
183 relative.length()==0 ? cn : relative+'/'+ cn,
184 listener);
185 }
186 }
187 return rewritten;
188 } finally {
189 callstack.remove(canonical);
190 }
191 }
192
193 /**
194 * Decides if this directory is worth visiting or not.
195 */
196 protected boolean isIgnoredDir(File dir) {
197 // ignoring the workspace and the artifacts directories. Both of them
198 // are potentially large and they do not store any secrets.
199 String n = dir.getName();
200 return n.equals("workspace") || n.equals("artifacts")
201 || n.equals("plugins") // no mutable data here
202 || n.equals("jenkins.security.RekeySecretAdminMonitor") // we don't want to rewrite backups
203 || n.equals(".") || n.equals("..");
204 }
205
206 private static boolean isBase64(char ch) {
207 return 0<=ch && ch<128 && IS_BASE64[ch];
208 }
209
210 private static boolean isBase64(String s) {
211 for (int i=0; i<s.length(); i++)
212 if (!isBase64(s.charAt(i)))
213 return false;
214 return true;
215 }
216
217 private static final boolean[] IS_BASE64 = new boolean[128];
218 static {
219 String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
220 for (int i=0; i<chars.length();i++)
221 IS_BASE64[chars.charAt(i)] = true;
222 }
223 }
224