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