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.pdp.test.TestBase;
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;
68 * TestCustom is an application that tests the extensibility and configurability of the AT&T XACML API.
70 * It creates a custom datatype definition factory that adds in custom data types for RSA
71 * PublicKey and PrivateKey.
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.
78 public class TestCustom extends TestBase {
79 private static final Log logger = LogFactory.getLog(TestCustom.class);
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";
88 public static final String DECRYPTION_INPUT_STRING = "This is the SECRET value!";
90 public static final String DECRYPTION_INPUT_ID = "com:att:research:xacml:test:custom:encrypted-data";
94 protected PublicKey publicKey = null;
95 protected PrivateKey privateKey = null;
97 // Our command line parameters
99 public static final String OPTION_GENERATE = "generate";
102 options.addOption(new Option(OPTION_GENERATE, false, "Generate a private/public key pair."));
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.
109 public void generateKeyPair() {
111 // Generate a RSA private/public key pair
113 KeyPairGenerator keyGen;
115 keyGen = KeyPairGenerator.getInstance(ALGORITHM);
116 } catch (NoSuchAlgorithmException e) {
117 logger.error("failed to generate keypair: " + e);
120 keyGen.initialize(1024);
121 final KeyPair key = keyGen.generateKeyPair();
123 // Save the keys to disk
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) {
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) {
139 public TestCustom(String[] args) throws ParseException, MalformedURLException, HelpException {
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.
149 * @see org.openecomp.policy.pdp.test.TestBase#parseCommands(java.lang.String[])
152 protected void parseCommands(String[] args) throws ParseException, MalformedURLException, HelpException {
154 // Have our parent class parse its options out
156 super.parseCommands(args);
158 // Parse the command line options
161 cl = new GnuParser().parse(options, args);
162 if (cl.hasOption(OPTION_GENERATE)) {
164 // Really only need to do this once to setup the test.
166 this.generateKeyPair();
172 * After our parent class configure's itself, all this needs to do is read in
173 * the public/private key's into objects.
175 * @see org.openecomp.policy.pdp.test.TestBase#configure()
178 protected void configure() throws FactoryException {
180 // Have our super do its thing
184 // Read in the public key
187 this.publicKey = (PublicKey) new ObjectInputStream(Files.newInputStream(Paths.get(this.directory, PUBLICKEY_FILE))).readObject();
188 } catch (ClassNotFoundException | IOException e) {
192 // Read in the private key
195 this.privateKey = (PrivateKey) new ObjectInputStream(Files.newInputStream(Paths.get(this.directory, PRIVATEKEY_FILE))).readObject();
196 } catch (ClassNotFoundException | IOException e) {
203 * Here we add 2 attributes into the request: 1) the private key, and 2) a String that was encrypted using the public key.
205 * The goal is to have the custom decrypt function use the private key to decrypt that string.
207 * @see org.openecomp.policy.pdp.test.TestBase#generateRequest(java.nio.file.Path, java.lang.String)
210 protected Request generateRequest(Path file, String group) throws JSONStructureException, DOMStructureException, PEPException {
212 // Have our super class do its work
214 Request oldRequest = super.generateRequest(file, group);
216 // Copy the request attributes
218 List<StdMutableRequestAttributes> attributes = new ArrayList<StdMutableRequestAttributes>();
219 for (RequestAttributes a : oldRequest.getRequestAttributes()) {
220 attributes.add(new StdMutableRequestAttributes(a));
223 // We are supplying the private key as an attribute for the decryption function to use:
225 // (NOTE: Ideally this would be provided by a custom PIP provider, not the PEP)
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
232 DataType<?> dtExtended = dataTypeFactory.getDataType(DataTypePrivateKey.DT_PRIVATEKEY);
233 if (dtExtended == null) {
234 logger.error("Failed to get private key datatype.");
238 // Create the attribute value
241 AttributeValue<?> attributeValue = dtExtended.createAttributeValue(this.privateKey);
243 // Create the attribute
245 StdMutableAttribute newAttribute = new StdMutableAttribute(XACML3.ID_SUBJECT_CATEGORY_ACCESS_SUBJECT,
246 new IdentifierImpl("com:att:research:xacml:test:custom:privatekey"),
248 "com:att:research:xacml:test:custom",
250 boolean added = false;
251 for (StdMutableRequestAttributes a : attributes) {
253 // Does the category exist?
255 if (a.getCategory().equals(XACML3.ID_SUBJECT_CATEGORY_ACCESS_SUBJECT)) {
257 // Yes - add in the new attribute value
264 if (added == false) {
266 // New category - create it and add it in
268 StdMutableRequestAttributes a = new StdMutableRequestAttributes();
269 a.setCategory(newAttribute.getCategory());
273 } catch (DataTypeException e) {
278 // We are also supplying this attribute which is the secret text encrypted with
281 // ID=com:att:research:xacml:test:custom:encrypted-data
283 // Category=urn:oasis:names:tc:xacml:1.0:subject-category:access-subject
284 // Datatype=http://www.w3.org/2001/XMLSchema#hexBinary
288 byte[] encryptedData = null;
290 Cipher cipher = Cipher.getInstance(ALGORITHM);
291 cipher.init(Cipher.ENCRYPT_MODE, this.publicKey);
293 // This is just a hack to test a decryption of the wrong value.
295 if (group.equals("Permit")) {
296 encryptedData = cipher.doFinal(DECRYPTION_INPUT_STRING.getBytes());
298 encryptedData = cipher.doFinal("This is NOT the secret".getBytes());
300 } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
305 // Sanity check (for the Permit request)
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.");
315 logger.error("Sanity check failed to decrypt the encrypted data.");
319 } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
323 // Get our datatype factory
325 dtExtended = dataTypeFactory.getDataType(XACML3.ID_DATATYPE_HEXBINARY);
326 if (dtExtended == null) {
327 logger.error("Failed to get hex binary datatype.");
331 // Create the attribute value
334 AttributeValue<?> attributeValue = dtExtended.createAttributeValue(encryptedData);
336 // Create the attribute
338 StdMutableAttribute newAttribute = new StdMutableAttribute(XACML3.ID_SUBJECT_CATEGORY_ACCESS_SUBJECT,
339 new IdentifierImpl("com:att:research:xacml:test:custom:encrypted-data"),
343 boolean added = false;
344 for (StdMutableRequestAttributes a : attributes) {
346 // Does the category exist?
348 if (a.getCategory().equals(XACML3.ID_SUBJECT_CATEGORY_ACCESS_SUBJECT)) {
350 // Yes - add in the new attribute value
357 if (added == false) {
359 // New category - create it and add it in
361 StdMutableRequestAttributes a = new StdMutableRequestAttributes();
362 a.setCategory(newAttribute.getCategory());
366 } catch (DataTypeException e) {
371 // Now form our final request
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) {
384 public static void main(String[] args) {
386 new TestCustom(args).run();
387 } catch (ParseException | IOException | FactoryException e) {
389 } catch (HelpException e) {