/******************************************************************************* * ============LICENSE_START==================================================== * * org.onap.aai * * =========================================================================== * * Copyright © 2017 AT&T Intellectual Property. All rights reserved. * * Copyright © 2017 Amdocs * * =========================================================================== * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * * ============LICENSE_END==================================================== * * * * ECOMP is a trademark and service mark of AT&T Intellectual Property. * * ******************************************************************************/ package com.att.cadi; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Random; import javax.crypto.CipherInputStream; import javax.crypto.CipherOutputStream; import com.att.cadi.Access.Level; import com.att.cadi.config.Config; /** * Key Conversion, primarily "Base64" * * Base64 is required for "Basic Authorization", which is an important part of the overall CADI Package. * * Note: This author found that there is not a "standard" library for Base64 conversion within Java. * The source code implementations available elsewhere were surprisingly inefficient, requiring, for * instance, multiple string creation, on a transaction pass. Integrating other packages that might be * efficient enough would put undue Jar File Dependencies given this Framework should have none-but-Java * dependencies. * * The essential algorithm is good for a symmetrical key system, as Base64 is really just * a symmetrical key that everyone knows the values. * * This code is quite fast, taking about .016 ms for encrypting, decrypting and even .08 for key * generation. The speed quality, especially of key generation makes this a candidate for a short term token * used for identity. * * It may be used to easily avoid placing Clear-Text passwords in configurations, etc. and contains * supporting functions such as 2048 keyfile generation (see keygen). This keyfile should, of course, * be set to "400" (Unix) and protected as any other mechanism requires. * * However, this algorithm has not been tested against hackers. Until such a time, utilize more tested * packages to protect Data, especially sensitive data at rest (long term). * */ public class Symm { private static final byte[] DOUBLE_EQ = new byte[] {'=','='}; public static final String ENC = "enc:"; private static final SecureRandom random = new SecureRandom(); public final char[] codeset; private final int splitLinesAt; private final String encoding; private final Convert convert; private final boolean endEquals; //Note: AES Encryption is not Thread Safe. It is Synchronized private static AES aes = null; // only initialized from File, and only if needed for Passwords /** * This is the standard base64 Key Set. * RFC 2045 */ public static final Symm base64 = new Symm( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray() ,76, Config.UTF_8,true); public static final Symm base64noSplit = new Symm( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray() ,Integer.MAX_VALUE, Config.UTF_8,true); /** * This is the standard base64 set suitable for URLs and Filenames * RFC 4648 */ public static final Symm base64url = new Symm( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".toCharArray() ,76, Config.UTF_8,true); /** * A Password set, using US-ASCII * RFC 4648 */ public static final Symm encrypt = new Symm(base64url.codeset,1024, "US-ASCII", false); /** * A typical set of Password Chars * Note, this is too large to fit into the algorithm. Only use with PassGen */ private static char passChars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+!@#$%^&*(){}[]?:;,.".toCharArray(); /** * Use this to create special case Case Sets and/or Line breaks * * If you don't know why you need this, use the Singleton Method * * @param codeset * @param split */ public Symm(char[] codeset, int split, String charset, boolean useEndEquals) { this.codeset = codeset; splitLinesAt = split; encoding = charset; endEquals = useEndEquals; char prev = 0, curr=0, first = 0; int offset=Integer.SIZE; // something that's out of range for integer array // There can be time efficiencies gained when the underlying keyset consists mainly of ordered // data (i.e. abcde...). Therefore, we'll quickly analyze the keyset. If it proves to have // too much entropy, the "Unordered" algorithm, which is faster in such cases is used. ArrayList la = new ArrayList(); for(int i=0;icodeset.length/3) { convert = new Unordered(codeset); } else { // too random to get speed enhancement from range algorithm int[][] range = new int[la.size()][]; la.toArray(range); convert = new Ordered(range); } } public Symm copy(int lines) { return new Symm(codeset,lines,encoding,endEquals); } // Only used by keygen, which is intentionally randomized. Therefore, always use unordered private Symm(char[] codeset, Symm parent) { this.codeset = codeset; splitLinesAt = parent.splitLinesAt; endEquals = parent.endEquals; encoding = parent.encoding; convert = new Unordered(codeset); } /** * Obtain the base64() behavior of this class, for use in standard BASIC AUTH mechanism, etc. * @return */ @Deprecated public static final Symm base64() { return base64; } /** * Obtain the base64() behavior of this class, for use in standard BASIC AUTH mechanism, etc. * No Line Splitting * @return */ @Deprecated public static final Symm base64noSplit() { return base64noSplit; } /** * Obtain the base64 "URL" behavior of this class, for use in File Names, etc. (no "/") */ @Deprecated public static final Symm base64url() { return base64url; } /** * Obtain a special ASCII version for Scripting, with base set of base64url use in File Names, etc. (no "/") */ public static final Symm baseCrypt() { return encrypt; } /* * Note: AES Encryption is NOT thread-safe. Must surround entire use with synchronized */ private synchronized void exec(AESExec exec) throws IOException { if(aes == null) { try { byte[] bytes = new byte[AES.AES_KEY_SIZE/8]; int offset = (Math.abs(codeset[0])+47)%(codeset.length-bytes.length); for(int i=0;i=0) { if(line>=splitLinesAt) { os.write('\n'); line = 0; } switch(++idx) { // 1 based reading, slightly faster ++ case 1: // ptr is the first 6 bits of read os.write(codeset[read>>2]); prev = read; break; case 2: // ptr is the last 2 bits of prev followed by the first 4 bits of read os.write(codeset[((prev & 0x03)<<4) | (read>>4)]); prev = read; break; default: //(3+) // Char 1 is last 4 bits of prev plus the first 2 bits of read // Char 2 is the last 6 bits of read os.write(codeset[(((prev & 0xF)<<2) | (read>>6))]); if(line==splitLinesAt) { // deal with line splitting for two characters os.write('\n'); line=0; } os.write(codeset[(read & 0x3F)]); ++line; idx = 0; prev = 0; } ++line; } else { // deal with any remaining bits from Prev, then pad switch(idx) { case 1: // just the last 2 bits of prev os.write(codeset[(prev & 0x03)<<4]); if(endEquals)os.write(DOUBLE_EQ); break; case 2: // just the last 4 bits of prev os.write(codeset[(prev & 0xF)<<2]); if(endEquals)os.write('='); break; } idx = 0; } } while(go); } public void decode(InputStream is, OutputStream os, int skip) throws IOException { is.skip(skip); decode(is,os); } /** * Decode InputStream onto OutputStream * @param is * @param os * @throws IOException */ public void decode(InputStream is, OutputStream os) throws IOException { int read, idx=0; int prev=0, index; while((read = is.read())>=0) { index = convert.convert(read); if(index>=0) { switch(++idx) { // 1 based cases, slightly faster ++ case 1: // index goes into first 6 bits of prev prev = index<<2; break; case 2: // write second 2 bits of into prev, write byte, last 4 bits go into prev os.write((byte)(prev|(index>>4))); prev = index<<4; break; case 3: // first 4 bits of index goes into prev, write byte, last 2 bits go into prev os.write((byte)(prev|(index>>2))); prev = index<<6; break; default: // (3+) | prev and last six of index os.write((byte)(prev|(index&0x3F))); idx = prev = 0; } } }; os.flush(); } /** * Interface to allow this class to choose which algorithm to find index of character in Key * */ private interface Convert { public int convert(int read) throws IOException; } /** * Ordered uses a range of orders to compare against, rather than requiring the investigation * of every character needed. * */ private static final class Ordered implements Convert { private int[][] range; public Ordered(int[][] range) { this.range = range; } public int convert(int read) throws IOException { switch(read) { case -1: case '=': case '\n': return -1; } for(int i=0;i= range[i][0] && read<=range[i][1]) { return read-range[i][2]; } } throw new IOException("Unacceptable Character in Stream"); } } /** * Unordered, i.e. the key is purposely randomized, simply has to investigate each character * until we find a match. * */ private static final class Unordered implements Convert { private char[] codec; public Unordered(char[] codec) { this.codec = codec; } public int convert(int read) throws IOException { switch(read) { case -1: case '=': case '\n': return -1; } for(int i=0;i=0) { index = o.next(); if(index<0 || index>=codeset.length) { System.out.println("uh, oh"); } if(right) { // alternate going left or right to find the next open slot (keeps it from taking too long to hit something) for(int j=index;j=0;--j) { if(seq[j]==0) { seq[j]=codeset[filled]; --filled; break; } } right = true; } } return new Symm(seq,this); } }