2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.openecomp.policy.pdp.test.custom;
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;
39 import javax.crypto.BadPaddingException;
40 import javax.crypto.Cipher;
41 import javax.crypto.IllegalBlockSizeException;
42 import javax.crypto.NoSuchPaddingException;
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;
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;
67 * TestCustom is an application that tests the extensibility and configurability of the AT&T XACML API.
69 * It creates a custom datatype definition factory that adds in custom data types for RSA
70 * PublicKey and PrivateKey.
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.
77 public class TestCustom extends TestBase {
78 private static final Logger logger = FlexLogger.getLogger(TestCustom.class);
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";
87 public static final String DECRYPTION_INPUT_STRING = "This is the SECRET value!";
89 public static final String DECRYPTION_INPUT_ID = "com:att:research:xacml:test:custom:encrypted-data";
93 protected PublicKey publicKey = null;
94 protected PrivateKey privateKey = null;
96 // Our command line parameters
98 public static final String OPTION_GENERATE = "generate";
101 options.addOption(new Option(OPTION_GENERATE, false, "Generate a private/public key pair."));
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.
108 public void generateKeyPair() {
110 // Generate a RSA private/public key pair
112 KeyPairGenerator keyGen;
114 keyGen = KeyPairGenerator.getInstance(ALGORITHM);
115 } catch (NoSuchAlgorithmException e) {
116 logger.error("failed to generate keypair: " + e);
119 keyGen.initialize(1024);
120 final KeyPair key = keyGen.generateKeyPair();
122 // Save the keys to disk
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);
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);
138 public TestCustom(String[] args) throws ParseException, MalformedURLException, HelpException {
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.
148 * @see org.openecomp.policy.pdp.test.TestBase#parseCommands(java.lang.String[])
151 protected void parseCommands(String[] args) throws ParseException, MalformedURLException, HelpException {
153 // Have our parent class parse its options out
155 super.parseCommands(args);
157 // Parse the command line options
160 cl = new GnuParser().parse(options, args);
161 if (cl.hasOption(OPTION_GENERATE)) {
163 // Really only need to do this once to setup the test.
165 this.generateKeyPair();
171 * After our parent class configure's itself, all this needs to do is read in
172 * the public/private key's into objects.
174 * @see org.openecomp.policy.pdp.test.TestBase#configure()
177 protected void configure() throws FactoryException {
179 // Have our super do its thing
183 // Read in the public key
186 this.publicKey = (PublicKey) new ObjectInputStream(Files.newInputStream(Paths.get(this.directory, PUBLICKEY_FILE))).readObject();
187 } catch (ClassNotFoundException | IOException e) {
191 // Read in the private key
194 this.privateKey = (PrivateKey) new ObjectInputStream(Files.newInputStream(Paths.get(this.directory, PRIVATEKEY_FILE))).readObject();
195 } catch (ClassNotFoundException | IOException e) {
202 * Here we add 2 attributes into the request: 1) the private key, and 2) a String that was encrypted using the public key.
204 * The goal is to have the custom decrypt function use the private key to decrypt that string.
206 * @see org.openecomp.policy.pdp.test.TestBase#generateRequest(java.nio.file.Path, java.lang.String)
209 protected Request generateRequest(Path file, String group) throws JSONStructureException, DOMStructureException, PEPException {
211 // Have our super class do its work
213 Request oldRequest = super.generateRequest(file, group);
215 // Copy the request attributes
217 List<StdMutableRequestAttributes> attributes = new ArrayList<>();
218 for (RequestAttributes a : oldRequest.getRequestAttributes()) {
219 attributes.add(new StdMutableRequestAttributes(a));
222 // We are supplying the private key as an attribute for the decryption function to use:
224 // (NOTE: Ideally this would be provided by a custom PIP provider, not the PEP)
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
231 DataType<?> dtExtended = dataTypeFactory.getDataType(DataTypePrivateKey.DT_PRIVATEKEY);
232 if (dtExtended == null) {
233 logger.error("Failed to get private key datatype.");
237 // Create the attribute value
240 AttributeValue<?> attributeValue = dtExtended.createAttributeValue(this.privateKey);
242 // Create the attribute
244 StdMutableAttribute newAttribute = new StdMutableAttribute(XACML3.ID_SUBJECT_CATEGORY_ACCESS_SUBJECT,
245 new IdentifierImpl("com:att:research:xacml:test:custom:privatekey"),
247 "com:att:research:xacml:test:custom",
249 boolean added = false;
250 for (StdMutableRequestAttributes a : attributes) {
252 // Does the category exist?
254 if (a.getCategory().equals(XACML3.ID_SUBJECT_CATEGORY_ACCESS_SUBJECT)) {
256 // Yes - add in the new attribute value
263 if (added == false) {
265 // New category - create it and add it in
267 StdMutableRequestAttributes a = new StdMutableRequestAttributes();
268 a.setCategory(newAttribute.getCategory());
272 } catch (DataTypeException e) {
277 // We are also supplying this attribute which is the secret text encrypted with
280 // ID=com:att:research:xacml:test:custom:encrypted-data
282 // Category=urn:oasis:names:tc:xacml:1.0:subject-category:access-subject
283 // Datatype=http://www.w3.org/2001/XMLSchema#hexBinary
287 byte[] encryptedData = null;
289 Cipher cipher = Cipher.getInstance(ALGORITHM);
290 cipher.init(Cipher.ENCRYPT_MODE, this.publicKey);
292 // This is just a hack to test a decryption of the wrong value.
294 if (group.equals("Permit")) {
295 encryptedData = cipher.doFinal(DECRYPTION_INPUT_STRING.getBytes());
297 encryptedData = cipher.doFinal("This is NOT the secret".getBytes());
299 } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
304 // Sanity check (for the Permit request)
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.");
314 logger.error("Sanity check failed to decrypt the encrypted data.");
318 } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
322 // Get our datatype factory
324 dtExtended = dataTypeFactory.getDataType(XACML3.ID_DATATYPE_HEXBINARY);
325 if (dtExtended == null) {
326 logger.error("Failed to get hex binary datatype.");
330 // Create the attribute value
333 AttributeValue<?> attributeValue = dtExtended.createAttributeValue(encryptedData);
335 // Create the attribute
337 StdMutableAttribute newAttribute = new StdMutableAttribute(XACML3.ID_SUBJECT_CATEGORY_ACCESS_SUBJECT,
338 new IdentifierImpl("com:att:research:xacml:test:custom:encrypted-data"),
342 boolean added = false;
343 for (StdMutableRequestAttributes a : attributes) {
345 // Does the category exist?
347 if (a.getCategory().equals(XACML3.ID_SUBJECT_CATEGORY_ACCESS_SUBJECT)) {
349 // Yes - add in the new attribute value
356 if (added == false) {
358 // New category - create it and add it in
360 StdMutableRequestAttributes a = new StdMutableRequestAttributes();
361 a.setCategory(newAttribute.getCategory());
365 } catch (DataTypeException e) {
370 // Now form our final request
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) {
383 public static void main(String[] args) {
385 new TestCustom(args).run();
386 } catch (ParseException | IOException | FactoryException e) {
388 } catch (HelpException e) {