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