1/*
2 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/*
25 * @test
26 * @bug 7152176 8168518 8172017
27 * @summary More krb5 tests
28 * @library ../../../../java/security/testlibrary/ /test/lib
29 * @run main/othervm/timeout=300 ReplayCacheTestProc
30 */
31
32import java.io.*;
33import java.nio.BufferUnderflowException;
34import java.nio.channels.SeekableByteChannel;
35import java.nio.file.Files;
36import java.nio.file.Paths;
37import java.nio.file.StandardCopyOption;
38import java.nio.file.StandardOpenOption;
39import java.security.MessageDigest;
40import java.security.NoSuchAlgorithmException;
41import java.util.*;
42import java.util.regex.Matcher;
43import java.util.regex.Pattern;
44
45import jdk.test.lib.Platform;
46import sun.security.jgss.GSSUtil;
47import sun.security.krb5.internal.rcache.AuthTime;
48
49/**
50 * This test runs multiple acceptor Procs to mimic AP-REQ replays.
51 * These system properties are supported:
52 *
53 * - test.libs on what types of acceptors to use
54 *   Format: CSV of (J|N|N<suffix>=<libname>|J<suffix>=<launcher>)
55 *   Default: J,N on Solaris and Linux where N is available, or J
56 *   Example: J,N,N14=/krb5-1.14/lib/libgssapi_krb5.so,J8=/java8/bin/java
57 *
58 * - test.runs on manual runs. If empty, a iterate through all pattern
59 *   Format: (req# | client# service#) acceptor# expected, ...
60 *   Default: null
61 *   Example: c0s0Jav,c1s1N14av,r0Jbx means 0th req is new c0->s0 sent to Ja,
62 *            1st req is new c1 to s1 sent to N14a,
63 *            2nd req is old (0th replayed) sent to Jb.
64 *            a/b at the end of acceptor is different acceptors of the same lib
65 *
66 * - test.autoruns on number of automatic runs
67 *   Format: number
68 *   Default: 100
69 */
70public class ReplayCacheTestProc {
71
72    private static Proc[] pa;   // all acceptors
73    private static Proc pi;     // the single initiator
74    private static List<Req> reqs = new ArrayList<>();
75    private static String HOST = "localhost";
76
77    private static final String SERVICE;
78    private static long uid;
79    private static String cwd;
80
81    static {
82        String tmp = System.getProperty("test.service");
83        SERVICE = (tmp == null) ? "service" : tmp;
84        uid = jdk.internal.misc.VM.geteuid();
85        // Where should the rcache be saved. KRB5RCACHEDIR is not
86        // recognized on Solaris (might be supported on Solaris 12),
87        // and directory name is different when launched by root.
88        // See manpage krb5envvar(5) on KRB5RCNAME.
89        if (System.getProperty("os.name").startsWith("SunOS")) {
90            if (uid == 0) {
91                cwd = "/var/krb5/rcache/root/";
92            } else {
93                cwd = "/var/krb5/rcache/";
94            }
95        } else {
96            cwd = System.getProperty("user.dir");
97        }
98    }
99
100    private static MessageDigest md5, sha256;
101
102    static {
103        try {
104            md5 = MessageDigest.getInstance("MD5");
105            sha256 = MessageDigest.getInstance("SHA-256");
106        } catch (NoSuchAlgorithmException nsae) {
107            throw new AssertionError("Impossible", nsae);
108        }
109    }
110
111
112    public static void main0(String[] args) throws Exception {
113        System.setProperty("java.security.krb5.conf", OneKDC.KRB5_CONF);
114        if (args.length == 0) { // The controller
115            int nc = 5;     // number of clients
116            int ns = 5;     // number of services
117            String[] libs;  // available acceptor types:
118                            // J: java
119                            // J<suffix>=<java launcher>: another java
120                            // N: default native lib
121                            // N<suffix>=<libname>: another native lib
122            Ex[] result;
123            int numPerType = 2; // number of acceptors per type
124
125            KDC kdc = KDC.create(OneKDC.REALM, HOST, 0, true);
126            for (int i=0; i<nc; i++) {
127                kdc.addPrincipal(client(i), OneKDC.PASS);
128            }
129            kdc.addPrincipalRandKey("krbtgt/" + OneKDC.REALM);
130            for (int i=0; i<ns; i++) {
131                kdc.addPrincipalRandKey(service(i));
132            }
133
134            kdc.writeKtab(OneKDC.KTAB);
135            KDC.saveConfig(OneKDC.KRB5_CONF, kdc);
136
137            // User-provided libs
138            String userLibs = System.getProperty("test.libs");
139
140            if (userLibs != null) {
141                libs = userLibs.split(",");
142            } else {
143                if (Platform.isOSX() || Platform.isWindows()) {
144                    // macOS uses Heimdal and Windows has no native lib
145                    libs = new String[]{"J"};
146                } else {
147                    if (acceptor("N", "sanity").waitFor() != 0) {
148                        Proc.d("Native mode sanity check failed, only java");
149                        libs = new String[]{"J"};
150                    } else {
151                        libs = new String[]{"J", "N"};
152                    }
153                }
154            }
155
156            pi = Proc.create("ReplayCacheTestProc").debug("C")
157                    .args("initiator")
158                    .start();
159
160            int na = libs.length * numPerType;  // total number of acceptors
161            pa = new Proc[na];
162
163            // Acceptors, numPerType for 1st, numForType for 2nd, ...
164            for (int i=0; i<na; i++) {
165                pa[i] = acceptor(libs[i/numPerType],
166                        "" + (char)('a' + i%numPerType));
167            }
168
169            // Manual runs
170            String userRuns = System.getProperty("test.runs");
171
172            if (userRuns == null) {
173                result = new Ex[Integer.parseInt(
174                        System.getProperty("test.autoruns", "100"))];
175                Random r = new Random();
176                for (int i = 0; i < result.length; i++) {
177                    boolean expected = reqs.isEmpty() || r.nextBoolean();
178                    result[i] = new Ex(
179                            i,
180                            expected ?
181                                    req(r.nextInt(nc), r.nextInt(ns)) :
182                                    r.nextInt(reqs.size()),
183                            pa[r.nextInt(na)],
184                            expected);
185                }
186            } else if (userRuns.isEmpty()) {
187                int count = 0;
188                result = new Ex[libs.length * libs.length];
189                for (int i = 0; i < libs.length; i++) {
190                    result[count] = new Ex(
191                            count,
192                            req(0, 0),
193                            pa[i * numPerType],
194                            true);
195                    count++;
196                    for (int j = 0; j < libs.length; j++) {
197                        if (i == j) {
198                            continue;
199                        }
200                        result[count] = new Ex(
201                                count,
202                                i,
203                                pa[j * numPerType],
204                                false);
205                        count++;
206                    }
207                }
208            } else {
209                String[] runs = userRuns.split(",");
210                result = new Ex[runs.length];
211                for (int i = 0; i < runs.length; i++) {
212                    UserRun run = new UserRun(runs[i]);
213                    result[i] = new Ex(
214                            i,
215                            run.req() == -1 ?
216                                    req(run.client(), run.service()) :
217                                    result[run.req()].req,
218                            Arrays.stream(pa)
219                                    .filter(p -> p.debug().equals(run.acceptor()))
220                                    .findFirst()
221                                    .orElseThrow(() -> new Exception(
222                                            "no acceptor named " + run.acceptor())),
223                            run.success());
224                }
225            }
226
227            for (Ex x : result) {
228                x.run();
229            }
230
231            pi.println("END");
232            for (int i=0; i<na; i++) {
233                pa[i].println("END");
234            }
235            System.out.println("\nAll Test Results\n================");
236            boolean finalOut = true;
237            System.out.println("        req**  client    service  acceptor   Result");
238            System.out.println("----  -------  ------  ---------  --------  -------");
239            for (int i=0; i<result.length; i++) {
240                boolean out = result[i].expected==result[i].actual;
241                finalOut &= out;
242                System.out.printf("%3d:    %3d%s      c%d    s%d %4s  %8s   %s  %s\n",
243                        i,
244                        result[i].req,
245                        result[i].expected ? "**" : "  ",
246                        reqs.get(result[i].req).client,
247                        reqs.get(result[i].req).service,
248                        "(" + result[i].csize + ")",
249                        result[i].acceptor.debug(),
250                        result[i].actual ? "++" : "--",
251                        out ? "   " : "xxx");
252            }
253
254            System.out.println("\nPath of Reqs\n============");
255            for (int j=0; ; j++) {
256                boolean found = false;
257                for (int i=0; i<result.length; i++) {
258                    if (result[i].req == j) {
259                        if (!found) {
260                            System.out.printf("%3d (c%s -> s%s): ", j,
261                                    reqs.get(j).client, reqs.get(j).service);
262                        }
263                        System.out.printf("%s%s(%d)%s",
264                                found ? " -> " : "",
265                                result[i].acceptor.debug(),
266                                i,
267                                result[i].actual != result[i].expected ?
268                                        "xxx" : "");
269                        found = true;
270                    }
271                }
272                System.out.println();
273                if (!found) {
274                    break;
275                }
276            }
277            if (!finalOut) throw new Exception();
278        } else if (args[0].equals("Nsanity")) {
279            // Native mode sanity check
280            Proc.d("Detect start");
281            Context s = Context.fromUserKtab("*", OneKDC.KTAB, true);
282            s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
283        } else if (args[0].equals("initiator")) {
284            while (true) {
285                String title = Proc.textIn();
286                Proc.d("Client see " + title);
287                if (title.equals("END")) break;
288                String[] cas = title.split(" ");
289                Context c = Context.fromUserPass(cas[0], OneKDC.PASS, false);
290                c.startAsClient(cas[1], GSSUtil.GSS_KRB5_MECH_OID);
291                c.x().requestCredDeleg(true);
292                byte[] token = c.take(new byte[0]);
293                Proc.d("Client AP-REQ generated");
294                Proc.binOut(token);
295            }
296        } else {
297            Proc.d(System.getProperty("java.vm.version"));
298            Proc.d(System.getProperty("sun.security.jgss.native"));
299            Proc.d(System.getProperty("sun.security.jgss.lib"));
300            Proc.d("---------------------------------\n");
301            Proc.d("Server start");
302            Context s = Context.fromUserKtab("*", OneKDC.KTAB, true);
303            Proc.d("Server login");
304            while (true) {
305                String title = Proc.textIn();
306                Proc.d("Server sees " + title);
307                if (title.equals("END")) break;
308                s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
309                byte[] token = Proc.binIn();
310                try {
311                    s.take(token);
312                    Proc.textOut("true");
313                    Proc.d("Good");
314                } catch (Exception e) {
315                    Proc.textOut("false");
316                    Proc.d("Bad");
317                }
318            }
319        }
320    }
321
322    public static void main(String[] args) throws Exception {
323        try {
324            main0(args);
325        } catch (Exception e) {
326            Proc.d(e);
327            throw e;
328        }
329    }
330
331    // returns the client name
332    private static String client(int p) {
333        return "client" + p;
334    }
335
336    // returns the service name
337    private static String service(int p) {
338        return SERVICE + p + "/" + HOST;
339    }
340
341    // returns the dfl name for a service
342    private static String dfl(int p) {
343        return SERVICE + p + (uid == -1 ? "" : ("_"+uid));
344    }
345
346    // generates an ap-req and save into reqs, returns the index
347    private static int req(int client, int service) throws Exception {
348        pi.println(client(client) + " " + service(service));
349        Req req = new Req(client, service, pi.readData());
350        reqs.add(req);
351        return reqs.size() - 1;
352    }
353
354    // create a acceptor
355    private static Proc acceptor(String type, String suffix) throws Exception {
356        Proc p;
357        String label;
358        String lib;
359        int pos = type.indexOf('=');
360        if (pos < 0) {
361            label = type;
362            lib = null;
363        } else {
364            label = type.substring(0, pos);
365            lib = type.substring(pos + 1);
366        }
367        if (type.startsWith("J")) {
368            if (lib == null) {
369                p = Proc.create("ReplayCacheTestProc");
370            } else {
371                p = Proc.create("ReplayCacheTestProc", lib);
372            }
373            p.prop("sun.security.krb5.rcache", "dfl")
374                    .prop("java.io.tmpdir", cwd);
375            String useMD5 = System.getProperty("jdk.krb5.rcache.useMD5");
376            if (useMD5 != null) {
377                p.prop("jdk.krb5.rcache.useMD5", useMD5);
378            }
379        } else {
380            p = Proc.create("ReplayCacheTestProc")
381                    .env("KRB5_CONFIG", OneKDC.KRB5_CONF)
382                    .env("KRB5_KTNAME", OneKDC.KTAB)
383                    .env("KRB5RCACHEDIR", cwd)
384                    .prop("sun.security.jgss.native", "true")
385                    .prop("javax.security.auth.useSubjectCredsOnly", "false")
386                    .prop("sun.security.nativegss.debug", "true");
387            if (lib != null) {
388                String libDir = lib.substring(0, lib.lastIndexOf('/'));
389                p.prop("sun.security.jgss.lib", lib)
390                        .env("DYLD_LIBRARY_PATH", libDir)
391                        .env("LD_LIBRARY_PATH", libDir);
392            }
393        }
394        Proc.d(label+suffix+" started");
395        return p.args(label+suffix).debug(label+suffix).start();
396    }
397
398    // generates hash of authenticator inside ap-req inside initsectoken
399    private static void record(String label, Req req) throws Exception {
400        byte[] data = Base64.getDecoder().decode(req.msg);
401        data = Arrays.copyOfRange(data, 17, data.length);
402
403        try (PrintStream ps = new PrintStream(
404                new FileOutputStream("log.txt", true))) {
405            ps.printf("%s:\nmsg: %s\nMD5: %s\nSHA-256: %s\n\n",
406                    label,
407                    req.msg,
408                    hex(md5.digest(data)),
409                    hex(sha256.digest(data)));
410        }
411    }
412
413    // Returns a compact hexdump for a byte array
414    private static String hex(byte[] hash) {
415        char[] h = new char[hash.length * 2];
416        char[] hexConst = "0123456789ABCDEF".toCharArray();
417        for (int i=0; i<hash.length; i++) {
418            h[2*i] = hexConst[(hash[i]&0xff)>>4];
419            h[2*i+1] = hexConst[hash[i]&0xf];
420        }
421        return new String(h);
422    }
423
424    // return size of dfl file, excluding the null hash ones
425    private static int csize(int p) throws Exception {
426        try (SeekableByteChannel chan = Files.newByteChannel(
427                Paths.get(cwd, dfl(p)), StandardOpenOption.READ)) {
428            chan.position(6);
429            int cc = 0;
430            while (true) {
431                try {
432                    if (AuthTime.readFrom(chan) != null) cc++;
433                } catch (BufferUnderflowException e) {
434                    break;
435                }
436            }
437            return cc;
438        } catch (IOException ioe) {
439            return 0;
440        }
441    }
442
443    // models an experiement
444    private static class Ex {
445        int i;              // #
446        int req;            // which ap-req to send
447        Proc acceptor;      // which acceptor to send to
448        boolean expected;   // expected result
449
450        boolean actual;     // actual output
451        int csize;          // size of rcache after test
452        String hash;        // the hash of req
453
454        Ex(int i, int req, Proc acceptor, boolean expected) {
455            this.i = i;
456            this.req = req;
457            this.acceptor = acceptor;
458            this.expected = expected;
459        }
460
461        void run() throws Exception {
462            Req r = reqs.get(req);
463            acceptor.println("TEST");
464            acceptor.println(r.msg);
465            String reply = acceptor.readData();
466
467            actual = Boolean.valueOf(reply);
468            csize = csize(r.service);
469
470            String label = String.format("%03d-client%d-%s%d-%s-%s",
471                    i, r.client, SERVICE, r.service, acceptor.debug(), actual);
472
473            record(label, r);
474            if (new File(cwd, dfl(r.service)).exists()) {
475                Files.copy(Paths.get(cwd, dfl(r.service)), Paths.get(label),
476                        StandardCopyOption.COPY_ATTRIBUTES);
477            }
478        }
479    }
480
481    // models a saved ap-req msg
482    private static class Req {
483        String msg;         // based64-ed req
484        int client;         // which client
485        int service;        // which service
486        Req(int client, int service, String msg) {
487            this.msg = msg;
488            this.client= client;
489            this.service = service;
490        }
491    }
492
493    private static class UserRun {
494        static final Pattern p
495                = Pattern.compile("(c(\\d)+s(\\d+)|r(\\d+))(.*)(.)");
496        final Matcher m;
497
498        UserRun(String run) { m = p.matcher(run); m.find(); }
499
500        int req() { return group(4); }
501        int client() { return group(2); }
502        int service() { return group(3); }
503        String acceptor() { return m.group(5); }
504        boolean success() { return m.group(6).equals("v"); }
505
506        int group(int i) {
507            String g = m.group(i);
508            return g == null ? -1 : Integer.parseInt(g);
509        }
510    }
511}
512