1 /*
2  * Copyright (c) 2021, Red Hat, Inc.
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 8258833
27  * @library /test/lib ..
28  * @modules jdk.crypto.cryptoki/sun.security.pkcs11:open
29  * @run main/othervm CancelMultipart
30  */
31 
32 import java.lang.reflect.Field;
33 import java.nio.ByteBuffer;
34 import java.security.Key;
35 import java.security.Provider;
36 import java.security.ProviderException;
37 import javax.crypto.Cipher;
38 import javax.crypto.IllegalBlockSizeException;
39 import javax.crypto.spec.SecretKeySpec;
40 
41 public class CancelMultipart extends PKCS11Test {
42 
43     private static Provider provider;
44     private static Key key;
45 
46     static {
47         key = new SecretKeySpec(new byte[16], "AES");
48     }
49 
50     private static class SessionLeaker {
51         private LeakOperation op;
52         private LeakInputType type;
53 
SessionLeaker(LeakOperation op, LeakInputType type)54         SessionLeaker(LeakOperation op, LeakInputType type) {
55             this.op = op;
56             this.type = type;
57         }
58 
leakAndTry()59         private void leakAndTry() throws Exception {
60             Cipher cipher = op.getCipher();
61             try {
62                 type.doOperation(cipher,
63                         (op instanceof LeakDecrypt ?
64                                 LeakInputType.DECRYPT_MODE :
65                                 null));
66                 throw new Exception("PKCS11Exception expected, invalid block"
67                         + "size");
68             } catch (ProviderException | IllegalBlockSizeException e) {
69                 // Exception expected - session returned to the SessionManager
70                 // should be cancelled. That's what will be tested now.
71             }
72 
73             tryCipherInit();
74         }
75     }
76 
77     private static interface LeakOperation {
getCipher()78         Cipher getCipher() throws Exception;
79     }
80 
81     private static interface LeakInputType {
82         static int DECRYPT_MODE = 1;
doOperation(Cipher cipher, int mode)83         void doOperation(Cipher cipher, int mode) throws Exception;
84     }
85 
86     private static class LeakDecrypt implements LeakOperation {
getCipher()87         public Cipher getCipher() throws Exception {
88             Cipher cipher = Cipher.getInstance(
89                     "AES/ECB/PKCS5Padding", provider);
90             cipher.init(Cipher.DECRYPT_MODE, key);
91             return cipher;
92         }
93     }
94 
95     private static class LeakByteBuffer implements LeakInputType {
doOperation(Cipher cipher, int mode)96         public void doOperation(Cipher cipher, int mode) throws Exception {
97             if (mode == DECRYPT_MODE) {
98                 cipher.update(ByteBuffer.allocate(1), ByteBuffer.allocate(1));
99                 cipher.doFinal(ByteBuffer.allocate(0), ByteBuffer.allocate(1));
100             }
101         }
102     }
103 
104     private static class LeakByteArray implements LeakInputType {
doOperation(Cipher cipher, int mode)105         public void doOperation(Cipher cipher, int mode) throws Exception {
106             if (mode == DECRYPT_MODE) {
107                 cipher.update(new byte[1]);
108                 cipher.doFinal(new byte[1], 0, 0);
109             }
110         }
111     }
112 
main(String[] args)113     public static void main(String[] args) throws Exception {
114         main(new CancelMultipart(), args);
115     }
116 
117     @Override
main(Provider p)118     public void main(Provider p) throws Exception {
119         init(p);
120 
121         // Try multiple paths:
122 
123         executeTest(new SessionLeaker(new LeakDecrypt(), new LeakByteArray()),
124                 "P11Cipher::implDoFinal(byte[], int, int)");
125 
126         executeTest(new SessionLeaker(new LeakDecrypt(), new LeakByteBuffer()),
127                 "P11Cipher::implDoFinal(ByteBuffer)");
128 
129         System.out.println("TEST PASS - OK");
130     }
131 
executeTest(SessionLeaker sl, String testName)132     private static void executeTest(SessionLeaker sl, String testName)
133             throws Exception {
134         try {
135             sl.leakAndTry();
136             System.out.println(testName +  ": OK");
137         } catch (Exception e) {
138             System.out.println(testName +  ": FAILED");
139             throw e;
140         }
141     }
142 
init(Provider p)143     private static void init(Provider p) throws Exception {
144         provider = p;
145 
146         // The max number of sessions is 2 because, in addition to the
147         // operation (i.e. PKCS11::getNativeKeyInfo), a session to hold
148         // the P11Key object is needed.
149         setMaxSessions(2);
150     }
151 
152     /*
153      * This method is intended to generate pression on the number of sessions
154      * to be used from the NSS Software Token, so sessions with (potentially)
155      * active operations are reused.
156      */
setMaxSessions(int maxSessions)157     private static void setMaxSessions(int maxSessions) throws Exception {
158         Field tokenField = Class.forName("sun.security.pkcs11.SunPKCS11")
159                 .getDeclaredField("token");
160         tokenField.setAccessible(true);
161         Field sessionManagerField = Class.forName("sun.security.pkcs11.Token")
162                 .getDeclaredField("sessionManager");
163         sessionManagerField.setAccessible(true);
164         Field maxSessionsField = Class.forName("sun.security.pkcs11.SessionManager")
165                 .getDeclaredField("maxSessions");
166         maxSessionsField.setAccessible(true);
167         Object sessionManagerObj = sessionManagerField.get(
168                 tokenField.get(provider));
169         maxSessionsField.setInt(sessionManagerObj, maxSessions);
170     }
171 
tryCipherInit()172     private static void tryCipherInit() throws Exception {
173         Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding", provider);
174 
175         // A CKR_OPERATION_ACTIVE error may be thrown if a session was
176         // returned to the Session Manager with an active operation, and
177         // we try to initialize the Cipher using it.
178         //
179         // Given that the maximum number of sessions was forced to 2, we know
180         // that the session to be used here was already used in a previous
181         // (failed) operation. Thus, the test asserts that the operation was
182         // properly cancelled.
183         cipher.init(Cipher.ENCRYPT_MODE, key);
184 
185         // If initialization passes, finish gracefully so other paths can
186         // be tested under the current maximum number of sessions.
187         cipher.doFinal(new byte[16], 0, 0);
188     }
189 }
190