Policy TestSuite Enabled
[policy/engine.git] / ECOMP-PDP / src / test / java / org / openecomp / policy / pdp / test / custom / TestCustom.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ECOMP-PDP
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.openecomp.policy.pdp.test.custom;
22
23 import java.io.IOException;
24 import java.io.ObjectInputStream;
25 import java.io.ObjectOutputStream;
26 import java.net.MalformedURLException;
27 import java.nio.file.Files;
28 import java.nio.file.Path;
29 import java.nio.file.Paths;
30 import java.security.InvalidKeyException;
31 import java.security.KeyPair;
32 import java.security.KeyPairGenerator;
33 import java.security.NoSuchAlgorithmException;
34 import java.security.PrivateKey;
35 import java.security.PublicKey;
36 import java.util.ArrayList;
37 import java.util.List;
38
39 import javax.crypto.BadPaddingException;
40 import javax.crypto.Cipher;
41 import javax.crypto.IllegalBlockSizeException;
42 import javax.crypto.NoSuchPaddingException;
43
44 import org.apache.commons.cli.CommandLine;
45 import org.apache.commons.cli.GnuParser;
46 import org.apache.commons.cli.Option;
47 import org.apache.commons.cli.ParseException;
48 import org.openecomp.policy.common.logging.flexlogger.FlexLogger;
49 import org.openecomp.policy.common.logging.flexlogger.Logger;
50
51 import com.att.research.xacml.api.AttributeValue;
52 import com.att.research.xacml.api.DataType;
53 import com.att.research.xacml.api.DataTypeException;
54 import com.att.research.xacml.api.Request;
55 import com.att.research.xacml.api.RequestAttributes;
56 import com.att.research.xacml.api.XACML3;
57 import com.att.research.xacml.api.pep.PEPException;
58 import com.att.research.xacml.std.IdentifierImpl;
59 import com.att.research.xacml.std.StdMutableAttribute;
60 import com.att.research.xacml.std.StdMutableRequest;
61 import com.att.research.xacml.std.StdMutableRequestAttributes;
62 import com.att.research.xacml.std.dom.DOMStructureException;
63 import com.att.research.xacml.std.json.JSONStructureException;
64 import com.att.research.xacml.util.FactoryException; 
65
66 /**
67  * TestCustom is an application that tests the extensibility and configurability of the AT&T XACML API.
68  * 
69  * It creates a custom datatype definition factory that adds in custom data types for RSA
70  * PublicKey and PrivateKey.
71  * 
72  * It creates a custom function definition factory that adds in custom decryption function for decrypting data. It
73  * also derives and loads custom functions for the RSA public/private key datatypes for the bag function: one-and-only. 
74  * 
75  *
76  */
77 public class TestCustom extends TestBase {
78         private static final Logger logger      = FlexLogger.getLogger(TestCustom.class);
79         
80         //
81         // Our public's
82         //
83         public static final String ALGORITHM = "RSA";
84         public static final String PRIVATEKEY_FILE = "PrivateKey.key";
85         public static final String PUBLICKEY_FILE = "PublicKey.key";
86         
87         public static final String DECRYPTION_INPUT_STRING = "This is the SECRET value!";
88         
89         public static final String DECRYPTION_INPUT_ID = "com:att:research:xacml:test:custom:encrypted-data";
90         //
91         // Our keys
92         //
93         protected PublicKey publicKey = null;
94         protected PrivateKey privateKey = null;
95         //
96         // Our command line parameters
97         //
98         public static final String OPTION_GENERATE = "generate";
99
100         static {
101                 options.addOption(new Option(OPTION_GENERATE, false, "Generate a private/public key pair."));
102         }
103         
104         /**
105          * This function generates the public/private key pair. Should never have to call this again, this was
106          * called once to generate the keys. They were saved into the testsets/custom/datatype-function sub-directory.
107          */
108         public void generateKeyPair() {
109                 //
110                 // Generate a RSA private/public key pair
111                 //
112                 KeyPairGenerator keyGen;
113                 try {
114                         keyGen = KeyPairGenerator.getInstance(ALGORITHM);
115                 } catch (NoSuchAlgorithmException e) {
116                         logger.error("failed to generate keypair: " + e);
117                         return;
118                 }
119                 keyGen.initialize(1024);
120                 final KeyPair key = keyGen.generateKeyPair();
121                 //
122                 // Save the keys to disk
123                 //
124                 Path file = Paths.get(this.directory, PRIVATEKEY_FILE);
125                 try (ObjectOutputStream os = new ObjectOutputStream(Files.newOutputStream(file))) {
126                         os.writeObject(key.getPrivate());
127                 } catch (IOException e) {
128                         logger.error("Exception Occured"+e);
129                 }
130                 file = Paths.get(this.directory, PUBLICKEY_FILE);
131                 try (ObjectOutputStream os = new ObjectOutputStream(Files.newOutputStream(file))) {
132                         os.writeObject(key.getPublic());
133                 } catch (IOException e) {
134                         logger.error("Exception Occured"+e);
135                 }
136         }
137
138         public TestCustom(String[] args) throws ParseException, MalformedURLException, HelpException {
139                 super(args);
140         }
141         
142         /* (non-Javadoc)
143          * 
144          * Simply look for command line option: -generate
145          * This generates the public/private key. Shouldn't need to call it again, the keys have
146          * already been generated and saved.
147          * 
148          * @see org.openecomp.policy.pdp.test.TestBase#parseCommands(java.lang.String[])
149          */
150         @Override
151         protected void parseCommands(String[] args) throws ParseException, MalformedURLException, HelpException {
152                 //
153                 // Have our parent class parse its options out
154                 //
155                 super.parseCommands(args);
156                 //
157                 // Parse the command line options
158                 //
159                 CommandLine cl;
160                 cl = new GnuParser().parse(options, args);
161                 if (cl.hasOption(OPTION_GENERATE)) {
162                         //
163                         // Really only need to do this once to setup the test.
164                         //
165                         this.generateKeyPair();
166                 }
167         }
168
169         /* (non-Javadoc)
170          * 
171          * After our parent class configure's itself, all this needs to do is read in
172          * the public/private key's into objects.
173          * 
174          * @see org.openecomp.policy.pdp.test.TestBase#configure()
175          */
176         @Override
177         protected void configure() throws FactoryException {
178                 //
179                 // Have our super do its thing
180                 //
181                 super.configure();
182                 //
183                 // Read in the public key
184                 //
185                 try {
186                         this.publicKey = (PublicKey) new ObjectInputStream(Files.newInputStream(Paths.get(this.directory, PUBLICKEY_FILE))).readObject();
187                 } catch (ClassNotFoundException | IOException e) {
188                         logger.error(e);
189                 }
190                 //
191                 // Read in the private key
192                 //
193                 try {
194                         this.privateKey = (PrivateKey) new ObjectInputStream(Files.newInputStream(Paths.get(this.directory, PRIVATEKEY_FILE))).readObject();
195                 } catch (ClassNotFoundException | IOException e) {
196                         logger.error(e);
197                 }
198         }
199
200         /* (non-Javadoc)
201          * 
202          * Here we add 2 attributes into the request: 1) the private key, and 2) a String that was encrypted using the public key.
203          * 
204          * The goal is to have the custom decrypt function use the private key to decrypt that string.
205          * 
206          * @see org.openecomp.policy.pdp.test.TestBase#generateRequest(java.nio.file.Path, java.lang.String)
207          */
208         @Override
209         protected Request generateRequest(Path file, String group) throws JSONStructureException, DOMStructureException, PEPException {
210                 //
211                 // Have our super class do its work
212                 //
213                 Request oldRequest = super.generateRequest(file, group);
214                 //
215                 // Copy the request attributes
216                 //
217                 List<StdMutableRequestAttributes> attributes = new ArrayList<StdMutableRequestAttributes>();
218                 for (RequestAttributes a : oldRequest.getRequestAttributes()) {
219                         attributes.add(new StdMutableRequestAttributes(a));
220                 }
221                 //
222                 // We are supplying the private key as an attribute for the decryption function to use:
223                 //
224                 // (NOTE: Ideally this would be provided by a custom PIP provider, not the PEP)
225                 //
226                 // ID=com:att:research:xacml:test:custom:privatekey
227                 // Issuer=com:att:research:xacml:test:custom
228                 // Category=urn:oasis:names:tc:xacml:1.0:subject-category:access-subject
229                 // Datatype=urn:com:att:research:xacml:custom:3.0:rsa:private
230                 //
231                 DataType<?> dtExtended = dataTypeFactory.getDataType(DataTypePrivateKey.DT_PRIVATEKEY);
232                 if (dtExtended == null) {
233                         logger.error("Failed to get private key datatype.");
234                         return null;
235                 }
236                 //
237                 // Create the attribute value
238                 //
239                 try {
240                         AttributeValue<?> attributeValue = dtExtended.createAttributeValue(this.privateKey);                                    
241                         //
242                         // Create the attribute
243                         //
244                         StdMutableAttribute newAttribute = new StdMutableAttribute(XACML3.ID_SUBJECT_CATEGORY_ACCESS_SUBJECT,
245                                                                                                                                                 new IdentifierImpl("com:att:research:xacml:test:custom:privatekey"),
246                                                                                                                                                 attributeValue,
247                                                                                                                                                 "com:att:research:xacml:test:custom",
248                                                                                                                                                 false);
249                         boolean added = false;
250                         for (StdMutableRequestAttributes a : attributes) {
251                                 //
252                                 // Does the category exist?
253                                 //
254                                 if (a.getCategory().equals(XACML3.ID_SUBJECT_CATEGORY_ACCESS_SUBJECT)) {
255                                         //
256                                         // Yes - add in the new attribute value
257                                         //
258                                         a.add(newAttribute);
259                                         added = true;
260                                         break;
261                                 }
262                         }
263                         if (added == false) {
264                                 //
265                                 // New category - create it and add it in
266                                 //
267                                 StdMutableRequestAttributes a = new StdMutableRequestAttributes(); 
268                                 a.setCategory(newAttribute.getCategory());
269                                 a.add(newAttribute);
270                                 attributes.add(a);
271                         }
272                 } catch (DataTypeException e) {
273                         logger.error(e);
274                         return null;
275                 }
276                 //
277                 // We are also supplying this attribute which is the secret text encrypted with
278                 // the public key.
279                 //
280                 // ID=com:att:research:xacml:test:custom:encrypted-data
281                 // Issuer=
282                 // Category=urn:oasis:names:tc:xacml:1.0:subject-category:access-subject
283                 // Datatype=http://www.w3.org/2001/XMLSchema#hexBinary
284                 //
285                 // Encrypt it
286                 //
287                 byte[] encryptedData = null;
288                 try {
289                         Cipher cipher = Cipher.getInstance(ALGORITHM);
290                         cipher.init(Cipher.ENCRYPT_MODE, this.publicKey);
291                         //
292                         // This is just a hack to test a decryption of the wrong value.
293                         //
294                         if (group.equals("Permit")) {
295                                 encryptedData = cipher.doFinal(DECRYPTION_INPUT_STRING.getBytes());
296                         } else {
297                                 encryptedData = cipher.doFinal("This is NOT the secret".getBytes());
298                         }
299                 } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
300                         logger.error(e);
301                         return null;
302                 }
303                 //
304                 // Sanity check (for the Permit request)
305                 //
306                 try {
307                         if (group.equals("Permit")) {
308                                 Cipher cipher = Cipher.getInstance(ALGORITHM);
309                                 cipher.init(Cipher.DECRYPT_MODE, this.privateKey);
310                                 byte[] decryptedData = cipher.doFinal(encryptedData);
311                                 if (new String(decryptedData).equals(DECRYPTION_INPUT_STRING)) {
312                                         logger.info("Sanity check passed: decrypted the encrypted data.");
313                                 } else {
314                                         logger.error("Sanity check failed to decrypt the encrypted data.");
315                                         return null;
316                                 }
317                         }
318                 } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
319                         logger.error(e);
320                 }
321                 //
322                 // Get our datatype factory
323                 //
324                 dtExtended = dataTypeFactory.getDataType(XACML3.ID_DATATYPE_HEXBINARY);
325                 if (dtExtended == null) {
326                         logger.error("Failed to get hex binary datatype.");
327                         return null;
328                 }
329                 //
330                 // Create the attribute value
331                 //
332                 try {
333                         AttributeValue<?> attributeValue = dtExtended.createAttributeValue(encryptedData);                                      
334                         //
335                         // Create the attribute
336                         //
337                         StdMutableAttribute newAttribute = new StdMutableAttribute(XACML3.ID_SUBJECT_CATEGORY_ACCESS_SUBJECT,
338                                                                                                                                                 new IdentifierImpl("com:att:research:xacml:test:custom:encrypted-data"),
339                                                                                                                                                 attributeValue,
340                                                                                                                                                 null,
341                                                                                                                                                 false);
342                         boolean added = false;
343                         for (StdMutableRequestAttributes a : attributes) {
344                                 //
345                                 // Does the category exist?
346                                 //
347                                 if (a.getCategory().equals(XACML3.ID_SUBJECT_CATEGORY_ACCESS_SUBJECT)) {
348                                         //
349                                         // Yes - add in the new attribute value
350                                         //
351                                         a.add(newAttribute);
352                                         added = true;
353                                         break;
354                                 }
355                         }
356                         if (added == false) {
357                                 //
358                                 // New category - create it and add it in
359                                 //
360                                 StdMutableRequestAttributes a = new StdMutableRequestAttributes(); 
361                                 a.setCategory(newAttribute.getCategory());
362                                 a.add(newAttribute);
363                                 attributes.add(a);
364                         }
365                 } catch (DataTypeException e) {
366                         logger.error(e);
367                         return null;
368                 }
369                 //
370                 // Now form our final request
371                 //
372                 StdMutableRequest newRequest = new StdMutableRequest();
373                 newRequest.setCombinedDecision(oldRequest.getCombinedDecision());
374                 newRequest.setRequestDefaults(oldRequest.getRequestDefaults());
375                 newRequest.setReturnPolicyIdList(oldRequest.getReturnPolicyIdList());
376                 newRequest.setStatus(oldRequest.getStatus());
377                 for (StdMutableRequestAttributes a : attributes) {
378                         newRequest.add(a);
379                 }
380                 return newRequest;
381         }
382
383         public static void main(String[] args) {
384                 try {
385                         new TestCustom(args).run();
386                 } catch (ParseException | IOException | FactoryException e) {
387                         logger.error(e);
388                 } catch (HelpException e) {
389                 }               
390         }
391
392 }