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.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;
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;
69 * TestCustom is an application that tests the extensibility and configurability of the AT&T XACML API.
71 * It creates a custom datatype definition factory that adds in custom data types for RSA
72 * PublicKey and PrivateKey.
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.
79 public class TestCustom extends TestBase {
80 private static final Logger logger = FlexLogger.getLogger(TestCustom.class);
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";
89 public static final String DECRYPTION_INPUT_STRING = "This is the SECRET value!";
91 public static final String DECRYPTION_INPUT_ID = "com:att:research:xacml:test:custom:encrypted-data";
95 protected PublicKey publicKey = null;
96 protected PrivateKey privateKey = null;
98 // Our command line parameters
100 public static final String OPTION_GENERATE = "generate";
103 options.addOption(new Option(OPTION_GENERATE, false, "Generate a private/public key pair."));
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.
110 public void generateKeyPair() {
112 // Generate a RSA private/public key pair
114 KeyPairGenerator keyGen;
116 keyGen = KeyPairGenerator.getInstance(ALGORITHM);
117 } catch (NoSuchAlgorithmException e) {
118 logger.error("failed to generate keypair: " + e);
121 keyGen.initialize(1024);
122 final KeyPair key = keyGen.generateKeyPair();
124 // Save the keys to disk
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) {
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) {
140 public TestCustom(String[] args) throws ParseException, MalformedURLException, HelpException {
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.
150 * @see org.openecomp.policy.pdp.test.TestBase#parseCommands(java.lang.String[])
153 protected void parseCommands(String[] args) throws ParseException, MalformedURLException, HelpException {
155 // Have our parent class parse its options out
157 super.parseCommands(args);
159 // Parse the command line options
162 cl = new GnuParser().parse(options, args);
163 if (cl.hasOption(OPTION_GENERATE)) {
165 // Really only need to do this once to setup the test.
167 this.generateKeyPair();
173 * After our parent class configure's itself, all this needs to do is read in
174 * the public/private key's into objects.
176 * @see org.openecomp.policy.pdp.test.TestBase#configure()
179 protected void configure() throws FactoryException {
181 // Have our super do its thing
185 // Read in the public key
188 this.publicKey = (PublicKey) new ObjectInputStream(Files.newInputStream(Paths.get(this.directory, PUBLICKEY_FILE))).readObject();
189 } catch (ClassNotFoundException | IOException e) {
193 // Read in the private key
196 this.privateKey = (PrivateKey) new ObjectInputStream(Files.newInputStream(Paths.get(this.directory, PRIVATEKEY_FILE))).readObject();
197 } catch (ClassNotFoundException | IOException e) {
204 * Here we add 2 attributes into the request: 1) the private key, and 2) a String that was encrypted using the public key.
206 * The goal is to have the custom decrypt function use the private key to decrypt that string.
208 * @see org.openecomp.policy.pdp.test.TestBase#generateRequest(java.nio.file.Path, java.lang.String)
211 protected Request generateRequest(Path file, String group) throws JSONStructureException, DOMStructureException, PEPException {
213 // Have our super class do its work
215 Request oldRequest = super.generateRequest(file, group);
217 // Copy the request attributes
219 List<StdMutableRequestAttributes> attributes = new ArrayList<StdMutableRequestAttributes>();
220 for (RequestAttributes a : oldRequest.getRequestAttributes()) {
221 attributes.add(new StdMutableRequestAttributes(a));
224 // We are supplying the private key as an attribute for the decryption function to use:
226 // (NOTE: Ideally this would be provided by a custom PIP provider, not the PEP)
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
233 DataType<?> dtExtended = dataTypeFactory.getDataType(DataTypePrivateKey.DT_PRIVATEKEY);
234 if (dtExtended == null) {
235 logger.error("Failed to get private key datatype.");
239 // Create the attribute value
242 AttributeValue<?> attributeValue = dtExtended.createAttributeValue(this.privateKey);
244 // Create the attribute
246 StdMutableAttribute newAttribute = new StdMutableAttribute(XACML3.ID_SUBJECT_CATEGORY_ACCESS_SUBJECT,
247 new IdentifierImpl("com:att:research:xacml:test:custom:privatekey"),
249 "com:att:research:xacml:test:custom",
251 boolean added = false;
252 for (StdMutableRequestAttributes a : attributes) {
254 // Does the category exist?
256 if (a.getCategory().equals(XACML3.ID_SUBJECT_CATEGORY_ACCESS_SUBJECT)) {
258 // Yes - add in the new attribute value
265 if (added == false) {
267 // New category - create it and add it in
269 StdMutableRequestAttributes a = new StdMutableRequestAttributes();
270 a.setCategory(newAttribute.getCategory());
274 } catch (DataTypeException e) {
279 // We are also supplying this attribute which is the secret text encrypted with
282 // ID=com:att:research:xacml:test:custom:encrypted-data
284 // Category=urn:oasis:names:tc:xacml:1.0:subject-category:access-subject
285 // Datatype=http://www.w3.org/2001/XMLSchema#hexBinary
289 byte[] encryptedData = null;
291 Cipher cipher = Cipher.getInstance(ALGORITHM);
292 cipher.init(Cipher.ENCRYPT_MODE, this.publicKey);
294 // This is just a hack to test a decryption of the wrong value.
296 if (group.equals("Permit")) {
297 encryptedData = cipher.doFinal(DECRYPTION_INPUT_STRING.getBytes());
299 encryptedData = cipher.doFinal("This is NOT the secret".getBytes());
301 } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
306 // Sanity check (for the Permit request)
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.");
316 logger.error("Sanity check failed to decrypt the encrypted data.");
320 } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
324 // Get our datatype factory
326 dtExtended = dataTypeFactory.getDataType(XACML3.ID_DATATYPE_HEXBINARY);
327 if (dtExtended == null) {
328 logger.error("Failed to get hex binary datatype.");
332 // Create the attribute value
335 AttributeValue<?> attributeValue = dtExtended.createAttributeValue(encryptedData);
337 // Create the attribute
339 StdMutableAttribute newAttribute = new StdMutableAttribute(XACML3.ID_SUBJECT_CATEGORY_ACCESS_SUBJECT,
340 new IdentifierImpl("com:att:research:xacml:test:custom:encrypted-data"),
344 boolean added = false;
345 for (StdMutableRequestAttributes a : attributes) {
347 // Does the category exist?
349 if (a.getCategory().equals(XACML3.ID_SUBJECT_CATEGORY_ACCESS_SUBJECT)) {
351 // Yes - add in the new attribute value
358 if (added == false) {
360 // New category - create it and add it in
362 StdMutableRequestAttributes a = new StdMutableRequestAttributes();
363 a.setCategory(newAttribute.getCategory());
367 } catch (DataTypeException e) {
372 // Now form our final request
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) {
385 public static void main(String[] args) {
387 new TestCustom(args).run();
388 } catch (ParseException | IOException | FactoryException e) {
390 } catch (HelpException e) {