001/*
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 2007 Sun Microsystems Inc. All Rights Reserved
005 *
006 * The contents of this file are subject to the terms
007 * of the Common Development and Distribution License
008 * (the License). You may not use this file except in
009 * compliance with the License.
010 *
011 * You can obtain a copy of the License at
012 * https://opensso.dev.java.net/public/CDDLv1.0.html or
013 * opensso/legal/CDDLv1.0.txt
014 * See the License for the specific language governing
015 * permission and limitations under the License.
016 *
017 * When distributing Covered Code, include this CDDL
018 * Header Notice in each file and include the License file
019 * at opensso/legal/CDDLv1.0.txt.
020 * If applicable, add the following below the CDDL Header,
021 * with the fields enclosed by brackets [] replaced by
022 * your own identifying information:
023 * "Portions Copyrighted [year] [name of copyright owner]"
024 *
025 * $Id: SecureAttrs.java,v 1.12 2009/03/31 17:18:10 exu Exp $
026 *
027 * Portions Copyrighted 2016 ForgeRock AS.
028 */
029
030package com.sun.identity.sae.api;
031
032
033import static org.forgerock.openam.utils.Time.*;
034
035import java.util.*;
036import java.io.*;
037import java.io.UnsupportedEncodingException;
038import java.security.MessageDigest;
039import java.security.NoSuchAlgorithmException;
040import sun.misc.CharacterEncoder;
041import com.sun.identity.shared.encode.Base64;
042import com.sun.identity.security.DataEncryptor;
043import java.security.*;
044import java.security.cert.X509Certificate;
045
046/**
047 * <code>SecureAttrs</code> class forms the core api of "Secure Attributes
048 * Exchange" (SAE) feature. The class uses off the shelf digital
049 * signing and encryption algorithms to generate tamperproof/nonrepudiable
050 * strings representing attribute maps and to verify these strings.
051 * Typical SAE usage is to securely send attributes (authentication &
052 * use profile data) from an asserting application (eg running on an IDP) to 
053 * a relying application (eg running on an SP). In this scenario the
054 * asserting party uses the "signing" interfaces to generate secure
055 * data and the relying application uses "verification" interfaces
056 * to ascertain the authenticity of the data.
057 * Current implementation provides two mechanisms to secure attributes :
058 *    Symmetric  : uses simple shared secrets between the two ends. 
059 *    Asymmetric : uses PKI based signing using public-private keys.
060 * Freshness is provided by a varying seed generated from the
061 * current timestamp and a configurable expiry period within which
062 * the relying party must validate the token.
063 * @supported.api
064 */
065public class SecureAttrs
066{
067    /**
068     *  HTTP parameter name used to send and receive secure attribute data. 
069     *  IDP : sends secure attrs in this parameter.
070     *  SP  : receives secure attrs in this parameter.
071     * @supported.api
072     */
073    public static final String SAE_PARAM_DATA     = "sun.data";
074
075    /**
076     *  SAE Parameter representing a command.
077     *  Currently only "logout" needs to be explicitly provided. SSO is implied.
078     *  IDP  : Uses this parameter to instruct FM to issue a global logout. 
079     *  SP   : Receives this parameter from FM.
080     * @supported.api
081     */
082
083    public static final String SAE_PARAM_CMD      = "sun.cmd";
084
085    /**
086     *  SAE Parameter representing the authenticated user.
087     *  IDP  : Uses this parameter to send authenticated userid to FM.
088     *  SP   : Receives userid in this parameter.
089     * @supported.api
090     */
091    public static final String SAE_PARAM_USERID   = "sun.userid";
092
093    /**
094     *  SAE Parameter representing the session's authentication level.
095     *  IDP  : Uses this parameter to send authentication level to FM.
096     *  SP   : Receives authentication level in this parameter.
097     * @supported.api
098     */
099    public static final String SAE_PARAM_AUTHLEVEL   = "sun.authlevel";
100
101    /**
102     *  SAE Parameter used to pass IDP entity ID to SP app.
103     *  IDP: Not Applicable
104     *  SP: populates this parameter to identify IDP used in SSO.
105     */
106    public static final String SAE_PARAM_IDPENTITYID = "sun.idpentityid";
107
108    /**
109     *  SAE Parameter used to pass SP entity ID to SP app.
110     *  IDP: Not Applicable
111     *  SP: populates this parameter to identify SP used in SSO.
112     */
113    public static final String SAE_PARAM_SPENTITYID = "sun.spentityid";
114
115    /**
116     *  SAE Parameter representing the requested SP app to be invoked.
117     *  IDP  : populates this parameter with SP side app to be invoked.
118     *  SP   : Not Applicable.
119     * @supported.api
120     */
121    public static final String SAE_PARAM_SPAPPURL = "sun.spappurl";
122
123    /**
124     *  SAE Parameter used to identify the IDP app (Asserting party)
125     *  IDP  : populates this parameter to identify itself.
126     *  SP   : Not Applicable.
127     * @supported.api
128     */
129    public static final String SAE_PARAM_IDPAPPURL = "sun.idpappurl";
130
131    /**
132     *  SAE Parameter : Deprecated.
133     * @supported.api
134     */
135    public static final String SAE_PARAM_APPID    = "sun.appid";
136
137    /**
138     *  SAE Parameter internally used by FM for storing token timestamp.
139     * @supported.api
140     */
141    public static final String SAE_PARAM_TS       = "sun.ts";
142
143    /**
144     *  SAE Parameter internally used by FM for storing signature data.
145     * @supported.api
146     */
147    public static final String SAE_PARAM_SIGN     = "sun.sign";
148
149    /**
150     *  SAE Parameter used to comunicate errors.
151     * @supported.api
152     */
153    public static final String SAE_PARAM_ERROR     = "sun.error";
154
155    /**
156     *  SAE Parameter used to communicate to SP to return to specified url 
157     *  upon Logout completion.
158     *  IDP : Not applicable
159     *  SP  : expected to redirect to the value upon processing logout req.
160     * @supported.api
161     */
162    public static final String SAE_PARAM_APPSLORETURNURL  = "sun.returnurl";
163
164    /**
165     *  SAE Parameter used to comunicate to FM where to redirect after a 
166     *  global logout is completed.
167     *  IDP : sends this param as part of logout command.
168     *  SP  : N/A.
169     * @supported.api
170     */
171    public static final String SAE_PARAM_APPRETURN  = "sun.appreturn";
172
173    /**
174     *  SAE command <code>SAE_PARAM_CMD</code>
175     * @supported.api
176     */
177    public static final String SAE_CMD_LOGOUT     = "logout";
178
179    /**
180     * Crypto types supported. 
181     * @supported.api
182     */
183    public static final String SAE_CRYPTO_TYPE = "type";
184
185    /**
186     * Crypto type : Symmetric : shared secret based trust between parties.
187     * @supported.api
188     */
189    public static final String SAE_CRYPTO_TYPE_ASYM = "asymmetric";
190
191    /**
192     * Crypto type : Asymmetric : PKI based trust.
193     * @supported.api
194     */
195    public static final String SAE_CRYPTO_TYPE_SYM = "symmetric";
196
197    /**
198     * SAE Config : classame implementing <code>Cert</code>.
199     * If not specified, a JKS keystore default impl is used.
200     */
201    public static final String SAE_CONFIG_CERT_CLASS = "certclassimpl";
202
203    /**
204     * SAE Config : Location of the keystore to access keys from for
205     *   asymmetric crypto.
206     * @supported.api
207     */
208    public static final String SAE_CONFIG_KEYSTORE_FILE = "keystorefile";
209
210    /**
211     *  SAE Config : keystore type. Default : JKS
212     * @supported.api
213     */
214    public static final String SAE_CONFIG_KEYSTORE_TYPE = "keystoretype";
215
216    /**
217     * SAE Config : Password to open the keystrore.
218     * @supported.api
219     */
220    public static final String SAE_CONFIG_KEYSTORE_PASS = "keystorepass";
221
222    /**
223     * SAE Config : Private key alias for asymmetric signing. Alias
224     *              is used to retrive the key from the keystore.
225     * @supported.api
226     */
227    public static final String SAE_CONFIG_PRIVATE_KEY_ALIAS = "privatekeyalias";
228
229    /**
230     * SAE Config : Public key for asymmetric signature verification. Alias
231     *              is used to retrive the key from the keystore.
232     * @supported.api
233     */
234    public static final String SAE_CONFIG_PUBLIC_KEY_ALIAS = "pubkeyalias";
235
236    /**
237     * SAE Config : Private key for asymmetric signing.
238     * @supported.api
239     */
240    public static final String SAE_CONFIG_PRIVATE_KEY = "privatekey";
241
242    /**
243     * SAE Config : Password to access the private key.
244     * @supported.api
245     */
246    public static final String SAE_CONFIG_PRIVATE_KEY_PASS = "privatekeypass";
247
248    /**
249     * SAE Config : Flag to indicate whether keys should be cached in memory
250     *       once retrieved from the keystore.
251     * @supported.api
252     */
253    public static final String SAE_CONFIG_CACHE_KEYS = "cachekeys";
254
255    /**
256     * SAE Config : shared secret constant - used internally in FM.
257     * @supported.api
258     */
259    public static final String SAE_CONFIG_SHARED_SECRET = "secret";
260
261    /**
262     * SAE Config : data encryption algorithm.
263     * @supported.api
264     */
265    public static final String SAE_CONFIG_DATA_ENCRYPTION_ALG =
266                                             "encryptionalgorithm";
267
268    /**
269     * SAE Config : data encryption key strength.
270     * @supported.api
271     */
272    public static final String SAE_CONFIG_ENCRYPTION_KEY_STRENGTH =
273                                         "encryptionkeystrength";
274
275    /**
276     * SAE Config :  Signature validity : since timetamp on signature.
277     * @supported.api
278     */
279    public static final String SAE_CONFIG_SIG_VALIDITY_DURATION =
280                                                "saesigvalidityduration";
281    /**
282     * Debug : true | false
283     */
284    public static boolean dbg = false;
285
286    private Certs certs = null;
287
288    private static HashMap instances = new HashMap();
289    private int tsDuration = 120000; // 2 minutes
290    private boolean asymsigning = false;
291    private boolean asymencryption = false;
292    private String dataEncAlg = "DES";
293    private int encKeyStrength = 56;
294
295    /**
296     * Returns an instance to perform crypto operations.
297     *  @param name 
298     *  @return <code>SecureAttrs</code> instance.
299     * @supported.api
300     */
301    public static synchronized SecureAttrs getInstance(String name)
302    {
303        return (SecureAttrs)instances.get(name);
304    }
305    /**
306     * Initializes a SecureAttrs instance specified by <code>name</code>.
307     * If the instance already exists, it replaces it with the new instance.
308     * Use <code>SecureAttrs.getIstance(name)</code> to obtain the instance.
309     * @param name Name of the <code>SecureAttrs</code> instance.
310     * @param type Cryptographic key type. Possible values are
311     *         <code>SecureAttrs.SAE_CRYPTO_TYPE_SYM<code>, and
312     *         <code>SecureAttrs.SAE_CRYPTO_TYPE_ASYM</code>
313     * @param properties : please see SAE_CONFIG_* constants for configurable 
314     *                     values.
315     * @throws Exception rethrows underlying exception.
316     * @supported.api
317     */
318    synchronized public static void init(
319         String name,  String type, Properties properties) throws Exception
320    {
321        SecureAttrs sa = new SecureAttrs(type, properties);
322        instances.put(name, sa);
323    }
324
325    /**
326     * Creates two instances of <code>SecureAttrs</code> named
327     * "symmetric" and "asymmetric" representing the two suppported
328     * crytp types.
329     * @param properties : please see SAE_CONFIG_* constants for configurable 
330     *                     values.
331     * @throws Exception rethrows underlying exception.
332     * @supported.api
333     * @deprecated For backward compatability with older releases of this api.
334     *     Replaced by {@link #init(String,String,Properties)}
335     */
336    synchronized public static void init(Properties properties) throws Exception
337    {
338        init(SAE_CRYPTO_TYPE_ASYM, SAE_CRYPTO_TYPE_ASYM, properties);
339        init(SAE_CRYPTO_TYPE_SYM, SAE_CRYPTO_TYPE_SYM, properties);
340    }
341
342    /**
343     * Returns a Base64 encoded string comprising a signed set of attributes.
344     *
345     *   @param attrs   Attribute Value pairs to be processed.
346     *   @param secret  Shared secret (symmetric) Private key alias (asymmetric)
347     *
348     *   @return Base64 encoded token String to be passed to a relying party.
349     * @supported.api
350     */
351    public String getEncodedString(Map attrs, String secret) throws Exception 
352    {
353        String signedAttrs = signAttributes(attrs, secret);
354        return Base64.encode(signedAttrs.getBytes("UTF-8"));
355    }
356
357    /**
358     *   Returns encrypted string for the given attributes. The encrypted
359     *   data is Base64 encoded string encrypted with supplied encryption
360     *   secret and signs using shared secret.
361     *   @param attrs   Attribute Value pairs to be processed.
362     *   @param secret  Shared secret (symmetric) Private key alias (asymmetric)     *   @param encSecret The encryption secret (symmetric) or Public
363     *                           Key alias (asymmetric)
364     *   @return Base64 encoded token String to be passed to a relying party.
365     * @supported.api
366     */
367    public String getEncodedString(Map attrs, String secret, String encSecret)
368           throws Exception {
369
370         if(encSecret == null) {
371            return getEncodedString(attrs, secret);
372         }
373
374         String signedString = signAttributes(attrs, secret);
375         String encryptedString = null;
376         if(asymencryption) {
377            Key encKey = getPublicKey(encSecret).getPublicKey();
378            encryptedString = DataEncryptor.encryptWithAsymmetricKey(
379                             signedString,
380                             dataEncAlg,
381                             encKeyStrength,
382                             encKey);
383         } else {
384            encryptedString = DataEncryptor.encryptWithSymmetricKey(
385                              signedString,
386                              dataEncAlg,
387                              secret);
388         }
389         if(dbg) {
390            System.out.println("SAE.getEncodedString: encrypted string" +
391                  encryptedString);
392         }
393         return encryptedString;
394    }
395
396    private String signAttributes(Map attrs, String secret) throws Exception {
397        if(attrs == null || attrs.isEmpty() ){
398           return null;
399        }
400
401        StringBuffer sb = new StringBuffer(200);
402        Iterator iter = attrs.entrySet().iterator();
403        while(iter.hasNext()) {
404           Map.Entry entry = (Map.Entry)iter.next();
405           String key = (String)entry.getKey();
406           String value = (String)entry.getValue();
407           sb.append(key).append("=").append(value).append("|");
408        }
409
410        sb.append("Signature=").append(getSignedString(attrs, secret));
411        return sb.toString();
412    }
413
414    /**
415     * Verifies a Base64 encoded string for authenticity based on the
416     * shared secret supplied.
417     *   @param str     Base64 encoded string containing attribute
418     *   @param secret  Shared secret (symmmetric) or Public Key (asymmetric)
419     *
420     *   @return        Decoded, verified and parsed attrbute name-valie pairs.
421     * @supported.api
422     */
423    public Map verifyEncodedString(String str, String secret) throws Exception
424    {
425        if(str == null) {
426            return null;
427        }
428
429        Map map = getRawAttributesFromEncodedData(str);
430        if (dbg) 
431            System.out.println("SAE:verifyEncodedString() : "+map);
432        String signatureValue = (String) map.remove("Signature");
433        if(!verifyAttrs(map, signatureValue, secret)) {
434            return null; 
435        }
436        return map; 
437    }
438
439    /**
440     * Verifies the encrypted data string using encryption secret and
441     * shared secret that was used for signing.
442     *   @param str     Base64 encoded string containing attribute
443     *   @param secret  Shared secret (symmmetric) or Public Key (asymmetric)
444     *   @param encSecret The encryption secret (symmetric) or Public
445     *                           Key alias (asymmetric)
446     *   @return        Decoded, verified and parsed attrbute name-valie pairs.
447     * @supported.api
448     */
449    public Map verifyEncodedString(String str, String secret, String encSecret)
450              throws Exception {
451
452         if(encSecret == null) {
453            return verifyEncodedString(str, secret);
454         }
455
456         if(!isEncrypted(str)) {
457            return verifyEncodedString(str, secret);
458         }
459         if (str.indexOf(' ') > 0) {
460             str = str.replace(' ', '+');
461         }
462         String decryptStr = null;
463         if(asymencryption) {
464            Key  pKey = certs.getPrivateKey(encSecret);
465            decryptStr = DataEncryptor.decryptWithAsymmetricKey(str,
466                         dataEncAlg, pKey);
467         } else {
468            decryptStr = DataEncryptor.decryptWithSymmetricKey(str,
469                         dataEncAlg, encSecret);
470         }
471         if (dbg) {
472            System.out.println("SAE:verifyEncodedString() : "+
473                "decrypted string " + decryptStr);
474         }
475         return verifyEncodedString(decryptStr, secret);
476
477    }
478
479    private boolean isEncrypted(String str) throws Exception {
480
481       if (str.indexOf(' ') > 0) {
482           str = str.replace(' ', '+');
483       }
484        byte[] decoded = Base64.decode(str);
485        byte[] encString = new byte[9];
486        for (int i=0; i < 9; i++) {
487           encString[i] = decoded[i];
488        }
489
490        String tmp = new String(encString, "UTF-8");
491        if(tmp.equals("ENCRYPTED")) {
492           return true;
493        }
494        return false;
495    }
496
497    /**
498     * Returns a decoded <code>Map</code> of attribute-value pairs. 
499     * No verification is performed. Useful when retrieving data before 
500     * verifying contents for authenticity.
501     *   @param str     Base64 encoded string containing attribute
502     *
503     *   @return        Decoded and parsed attrbute name-value pairs.
504     * @supported.api
505     */
506    public Map getRawAttributesFromEncodedData(String str) throws Exception
507    {
508        if(str == null) {
509           return null;
510        }
511
512        if (str.indexOf(' ') > 0)
513            str = str.replace(' ', '+');
514        byte[] bytes = Base64.decode(str); 
515        String decoded = new String(bytes, "UTF-8");
516        if(decoded.indexOf("|") == -1) {
517            return null;
518        }
519        StringTokenizer tokenizer = new StringTokenizer(decoded, "|");
520
521        Map map = new HashMap();
522        while(tokenizer.hasMoreTokens()) {
523            String st = tokenizer.nextToken(); 
524            int index = st.indexOf("=");
525            if(index == -1) {
526               continue;
527            }
528            String attr = st.substring(0, index); 
529            String value = st.substring(index+1, st.length());
530            map.put(attr, value);
531        }
532
533        return map; 
534    }
535
536    /**
537     * Returns a decoded <code>Map</code> of attribute-value pairs.
538     * No verification is performed. Useful when retrieving data before
539     * verifying contents for authenticity.
540     *   @param str     Base64 encoded string containing attribute
541     *   @param encSecret The encryption secret (symmetric) or Public
542     *                           Key alias (asymmetric)
543     *   @return        Decoded and parsed attrbute name-value pairs.
544     * @supported.api
545     */
546    public Map getRawAttributesFromEncodedData(String str, String encSecret)
547                 throws Exception {
548
549        if(encSecret == null) {
550           return getRawAttributesFromEncodedData(str);
551        }
552        if (str.indexOf(' ') > 0) {
553            str = str.replace(' ', '+');
554        }
555        if(!isEncrypted(str)) {
556           return getRawAttributesFromEncodedData(str);
557        }
558        String decryptStr = null;
559        if(asymencryption) {
560           Key  pKey = certs.getPrivateKey(encSecret);
561           decryptStr = DataEncryptor.decryptWithAsymmetricKey(str,
562                        dataEncAlg, pKey);
563        } else {
564           decryptStr = DataEncryptor.decryptWithSymmetricKey(str,
565                        dataEncAlg, encSecret);
566        }
567        if(dbg) {
568           System.out.println("SAE.getRawAttributes() decrypted" +
569              " string" + decryptStr);
570        }
571        return getRawAttributesFromEncodedData(decryptStr);
572    }
573
574    /** 
575     * This interface allows to set the private to be used for signing
576     * as an alternative to passing down <code>SAE_CONFIG_PRIVATE_KEY_ALIAS</a>
577     * via <code>init</code>. Use this interface if you do not want
578     * SecureAttr to obtain the signing key from a configured keystore.
579     * To use this key during signing, specify secret as null.
580     * @param privatekey
581     * @supported.api
582     */
583    public void setPrivateKey(PrivateKey privatekey)
584    {
585        certs.setPrivatekey(privatekey);
586    }
587
588    /** 
589     * This interface allows to register a public key to be used for signature
590     * verification. Use this interface if you do not want SecureAttrs to
591     * obtain public keys from a configured keystore.
592     * @param pubkeyalias
593     * @param x509certificate instance.
594     * @supported.api
595     */
596    public void addPublicKey(
597                       String pubkeyalias, X509Certificate x509certificate)
598    {
599        certs.addPublicKey(pubkeyalias, x509certificate);
600    }
601
602    private X509Certificate getPublicKey(String alias)
603    {
604        return certs.getPublicKey(alias);
605    }
606
607    /**
608     * Returns a String representing data in the attrs argument.
609     * The String generated can be one of the following depending
610     * on configuration  :
611     *   SHA1 digest based on a shared secret and current timestamp.
612     *   or
613     *   Digital signature based on a configured certificate key.
614     *
615     *   @param attrs   List of attribute Value pairs to be processed.
616     *   @param secret  Shared secret (symmmetric) or Private Key (asymmetric)
617     *
618     *   @return        token String to be passed to a relying party.
619     * @supported.api
620     */
621    public String getSignedString(Map attrs, String secret) throws Exception
622    {
623        // Normalize     
624        StringBuffer str = normalize(attrs);
625        // Setup a fresh timestamp
626        long timestamp = (newDate()).getTime();
627
628        String signature = null;
629
630        if(asymsigning)
631        {
632            PrivateKey pKey = certs.getPrivateKey(secret);
633            signature = signAsym(str.append(timestamp).toString(), pKey);
634        } else
635        {
636            // Create seed : TIMESTAMP + shared secret
637            String seed = secret+timestamp;
638            // Encrypt
639            signature = encrypt(str+seed, seed);
640        }
641        if (signature == null) {
642            return null;
643        }
644        return ("TS"+timestamp + "TS"+signature);
645    }
646
647    /**
648     * Verifies the authenticity of data the attrs argument based
649     * on the token presented. Both attrs and token is sent by
650     * a asserting party. 
651     *   @param attrs   List of attribute Value pairs to be processed.
652     *   @param token   token represnting attrs provided by asserting party.
653     *   @param secret  Shared secret (symmmetric) or Public Key (asymmetric)
654     *
655     *   @return true  if attrs and token verify okay, else returns false.
656     * @supported.api
657     */
658    public boolean verifyAttrs(Map attrs, String token, String secret) 
659                               throws Exception
660    {
661        // Normalize     
662        StringBuffer str = normalize(attrs);
663        // Retrieve timestamp
664        int idx = token.indexOf("TS", 2);
665        String ts = token.substring(2, idx);
666        long signts = Long.parseLong(ts);
667        long nowts = (newDate()).getTime();
668
669        // Check timestamp validity
670        if ((nowts - signts) > tsDuration)
671            return false;
672
673        if(asymsigning)
674        {
675            String signature = token.substring(idx + 2, token.length());
676            return verifyAsym(str.append(ts).toString(), 
677                              signature, getPublicKey(secret));
678        }
679        // Create seed : TIMESTAMP + shared secret
680        String seed = secret + ts;
681        // Encrypt
682        String newstr ="TS"+ts+ "TS"+encrypt(str+seed, seed);
683        if (token.equals(newstr) )
684            return true;
685        else
686            return false;
687    }
688
689
690    private SecureAttrs(String type, Properties properties) throws Exception
691    {
692        if (SAE_CRYPTO_TYPE_ASYM.equals(type)) {
693            asymsigning = true;
694            asymencryption = true;
695        }
696        String dur = properties.getProperty(SAE_CONFIG_SIG_VALIDITY_DURATION);
697        if (dur != null) 
698            tsDuration = Integer.parseInt(dur);
699
700        String clzName = properties.getProperty(SAE_CONFIG_CERT_CLASS);
701        if (clzName != null)
702            certs = (Certs) Class.forName(clzName).newInstance();
703            //"com.sun.identity.sae.api.FMCerts").newInstance();
704        else
705            certs = new DefaultCerts();
706
707        certs.init(properties);
708        dataEncAlg = (String)properties.get(SAE_CONFIG_DATA_ENCRYPTION_ALG);
709        String tmp = (String)properties.get(
710                          SAE_CONFIG_ENCRYPTION_KEY_STRENGTH);
711        if(tmp != null) {
712           encKeyStrength = (new Integer(tmp)).intValue();
713        }
714    }
715
716    private StringBuffer normalize(Map attrs)
717    {
718        // Sort the Map
719        TreeMap smap = new TreeMap(attrs);
720        
721        // Flatten to a single String
722        StringBuffer str = new StringBuffer();
723        Iterator iter = smap.keySet().iterator();      
724        while (iter.hasNext()) {
725            String key = (String) iter.next();
726            str.append(key).append("=").append(smap.get(key)).append("|");
727        }
728        return str;
729    }  
730
731    private synchronized String encrypt(String plaintext, 
732              String seed) throws Exception
733    {
734        MessageDigest md = null;
735        try
736        {
737            md = MessageDigest.getInstance("SHA"); //step 2
738        } catch(NoSuchAlgorithmException e) {
739            throw new Exception(e.getMessage());
740        }
741        try
742        {
743            md.update((plaintext).getBytes("UTF-8")); //step 3
744        } catch(UnsupportedEncodingException e) {
745            throw new Exception(e.getMessage());
746        }
747
748        byte raw[] = md.digest(); //step 4
749        String hash = Base64.encode(raw);
750
751        return hash; //step 6
752    }
753    private String signAsym(String s, PrivateKey privatekey)
754    {
755        if(s == null || s.length() == 0 || privatekey == null) {
756            if (dbg)
757                System.out.println("SAE : signAsym: returning since priv key null");
758            return null;
759        }
760        String s1 = privatekey.getAlgorithm();
761        Signature signature = null;
762        Object obj = null;
763        if(s1.equals("RSA"))
764            try
765            {
766                signature = Signature.getInstance("SHA1withRSA");
767                String s2 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
768            }
769            catch(Exception exception)
770            {
771                System.out.println("SAE:asym sign : RSA failed ="+exception);
772                return null;
773            }
774        else
775        if(s1.equals("DSA"))
776        {
777            try
778            {
779                signature = Signature.getInstance("SHA1withDSA");
780                String s3 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1";
781            }
782            catch(Exception exception1)
783            {
784                System.out.println("SAE:asym sign : DSA failed ="+exception1);
785                return null;
786            }
787        } else
788        {
789            System.out.println("SAE:asym sign : No Algorithm");
790            return null;
791        }
792        try
793        {
794            signature.initSign(privatekey);
795        }
796        catch(Exception exception2)
797        {
798            System.out.println("SAE:asym sign : sig.initSign failed"+exception2);
799            return null;
800        }
801        try
802        {
803            System.out.println("Query str:"+s);
804            signature.update(s.getBytes());
805        }
806        catch(Exception exception3)
807        {
808            System.out.println("SAE:asym sign : sig.update failed"+exception3);
809            return null;
810        }
811        byte abyte0[] = null;
812        try
813        {
814            abyte0 = signature.sign();
815        }
816        catch(Exception exception4)
817        {
818            System.out.println("SAE:asym sign : sig.sign failed"+exception4);
819            return null;
820        }
821        if(abyte0 == null || abyte0.length == 0)
822        {
823            System.out.println("SAE:asym sign : sigBytes null");
824            return null;
825        } else
826        {
827            String s4 = Base64.encode(abyte0);
828            System.out.println("B64 Signature="+s4);
829            return s4;
830        }
831    }
832
833    private boolean verifyAsym(String s, String s1, X509Certificate x509certificate)
834    {
835        if(s == null || s.length() == 0 || x509certificate == null || s1 == null)
836        {
837            if (dbg)
838            System.out.println("SAE:asym verify: qstring or cert or signature is null");
839            return false;
840        }
841        byte abyte0[] = Base64.decode(s1);
842        if (dbg)
843          System.out.println("SAE:verifyAsym:signature="+abyte0+" origstr="+s1);
844        Object obj = null;
845        Object obj1 = null;
846        String s2 = x509certificate.getPublicKey().getAlgorithm();
847        Signature signature = null;
848        if(s2.equals("DSA"))
849            try
850            {
851                signature = Signature.getInstance("SHA1withDSA");
852            }
853            catch(Exception exception)
854            {
855                System.out.println("SAE:asym verify : DSA instance"+exception);
856                exception.printStackTrace();
857                return false;
858            }
859        else
860        if(s2.equals("RSA"))
861        {
862            try
863            {
864                signature = Signature.getInstance("SHA1withRSA");
865            }
866            catch(Exception exception1)
867            {
868                System.out.println("SAE:asym verify : RSA instance"+exception1);
869                exception1.printStackTrace();
870                return false;
871            }
872        } else
873        {
874            System.out.println("SAE:asym verify : no instance");
875            return false;
876        }
877        try
878        {
879            signature.initVerify(x509certificate);
880        }
881        catch(Exception exception2)
882        {
883            System.out.println("SAE:asym verify :sig.initVerify"+exception2);
884            exception2.printStackTrace();
885            return false;
886        }
887        try
888        {
889            signature.update(s.getBytes());
890        }
891        catch(Exception exception3)
892        {
893            System.out.println("SAE:asym verify :sig.update:"+exception3+" sig="+abyte0);
894            exception3.printStackTrace();
895            return false;
896        }
897        boolean flag = false;
898        try
899        {
900            flag = signature.verify(abyte0);
901        }
902        catch(Exception exception4)
903        {
904            System.out.println("SAE:asym verify :sig.verify:"+exception4+"sig="+abyte0);
905            exception4.printStackTrace();
906            return false;
907        }
908        return flag;
909    }
910
911    static public void main(String[] args)
912    {
913        try
914        {
915            SecureAttrs.dbg = true;
916            Properties properties = new Properties();
917            properties.setProperty("keystorefile", "mykeystore");
918            properties.setProperty("keystoretype", "JKS");
919            properties.setProperty("keystorepass", "22222222");
920            properties.setProperty("privatekeyalias", "test");
921            properties.setProperty("publickeyalias", "test");
922            properties.setProperty("privatekeypass", "22222222");
923            properties.setProperty("encryptionkeystrength", "56");
924            properties.setProperty("encryptionalgorithm", "DES");
925
926            SecureAttrs.init("testsym", SecureAttrs.SAE_CRYPTO_TYPE_SYM, 
927                             properties);
928            SecureAttrs.init("testasym", SecureAttrs.SAE_CRYPTO_TYPE_ASYM, 
929                             properties);
930            System.out.println("TEST 1 START test encoded str ===========");
931            SecureAttrs secureattrs = SecureAttrs.getInstance("testsym");
932            String s = "YnJhbmNoPTAwNXxtYWlsPXVzZXI1QG1haWwuY29tfHN1bi51c2VyaWQ9dXNlcjV8U2lnbmF0dXJlPVRTMTE3NDI3ODY1OTM2NlRTbzI2MkhoL3R1dDRJc0U1V3ZqWjVSLzZkM0FzPQ==";
933            Map map = secureattrs.verifyEncodedString(s, "secret");
934            if(map == null)
935                System.out.println("    FAILED");
936            else
937                System.out.println("    PASSED"+map);
938            System.out.println("TEST 1 END ================");
939            System.out.println("TEST 2 START : encode followed by decode ===");
940            HashMap hashmap = new HashMap();
941            hashmap.put("branch", "bb");
942            hashmap.put("mail", "mm");
943            hashmap.put("sun.userid", "uu");
944            hashmap.put("sun.spappurl", "apapp");
945            System.out.println("  TEST 2a START : SYM KEY ===");
946            secureattrs = SecureAttrs.getInstance("testsym");
947            String s1 = "secret";
948            String s2 = secureattrs.getEncodedString(hashmap, s1);
949            System.out.println("Encoded string: "+s2);
950            Map map1 = secureattrs.verifyEncodedString(s2, s1);
951            if(map1 != null)
952                System.out.println("  2a PASSED "+map1);
953            else
954                System.out.println("  2a FAILED "+map1);
955            System.out.println("  TEST 2b START : ASYM KEY ===");
956            secureattrs = getInstance("testasym");
957            s1 = "test";
958            String s3 = secureattrs.getEncodedString(hashmap, s1);
959            System.out.println("Encoded string: "+s3);
960            map1 = secureattrs.verifyEncodedString(s3, s1);
961            if(map1 != null)
962                System.out.println("  2b PASSED "+map1);
963            else
964                System.out.println("  2b FAILED "+map1);
965            System.out.println("TEST 2 END  ====================");
966            System.out.println("TEST 3 START : decode with incorrect secret");
967            System.out.println("  TEST 3a START : SYM KEY ===");
968            secureattrs = getInstance("testsym");
969            map1 = secureattrs.verifyEncodedString(s2, "junk");
970            if(map1 != null)
971                System.out.println("  3a FAILED "+map1);
972            else
973                System.out.println("  3a PASSED "+map1);
974            System.out.println("  TEST 3b START : ASYM KEY ===");
975            secureattrs = getInstance("testasym");
976            map1 = secureattrs.verifyEncodedString(s3, "junk");
977            if(map1 != null)
978                System.out.println("  3b FAILED "+map1);
979            else
980                System.out.println("  3b PASSED "+map1);
981            System.out.println("TEST 3 END  ====================");
982            System.out.println("TEST 4 START : decode with correct secret");
983            System.out.println("  TEST 4a START : SYM KEY ===");
984            secureattrs = getInstance("testsym");
985            s1 = "secret";
986            map1 = secureattrs.verifyEncodedString(s2, s1);
987            if(map1 != null)
988                System.out.println("  4a PASSED "+map1);
989            else
990                System.out.println("  4a FAILED "+map1);
991            System.out.println("  TEST 4b START : ASYM KEY ===");
992            secureattrs = getInstance("testasym");
993            s1 = "test";
994            map1 = secureattrs.verifyEncodedString(s3, s1);
995            if(map1 != null)
996                System.out.println("  4a PASSED "+map1);
997            else
998                System.out.println("  4a FAILED "+map1);
999            System.out.println("TEST 4 END  ====================");
1000            System.out.println("  TEST 5a START : ASYM KEY ===");
1001            secureattrs = getInstance("testasym");
1002            s1 = "test";
1003            s3 = secureattrs.getEncodedString(hashmap, s1, s1);
1004            System.out.println("Encrypted string: "+s3);
1005            map1 = secureattrs.verifyEncodedString(s3, s1, s1);
1006            if(map1 != null)
1007                System.out.println("  5a PASSED "+map1);
1008            else
1009                System.out.println("  5a FAILED "+map1);
1010            System.out.println("  TEST 5b START : SYM KEY ===");
1011            secureattrs = SecureAttrs.getInstance("testsym");
1012            s1 = "secret";
1013            s2 = secureattrs.getEncodedString(hashmap, s1, s1);
1014            System.out.println("Encrypted string: "+s2);
1015            map1 = secureattrs.verifyEncodedString(s2, s1, s1);
1016            if(map1 != null)
1017                System.out.println("  5b PASSED "+map1);
1018            else
1019                System.out.println("  5b FAILED "+map1);
1020            System.out.println("TEST 5 END  ====================");
1021        }
1022        catch(Exception exception)
1023        {
1024            exception.printStackTrace();
1025            System.out.println("TEST Exc : "+exception);
1026        }
1027     
1028    }
1029    public interface Certs {
1030        public void init(Properties props) throws Exception;
1031        public PrivateKey getPrivateKey(String alias);
1032        public X509Certificate getPublicKey(String alias);
1033        public void setPrivatekey(PrivateKey privatekey);
1034        public void addPublicKey(
1035                       String pubkeyalias, X509Certificate x509certificate);
1036    }
1037    static class DefaultCerts implements Certs
1038    {
1039        private PrivateKey privateKey = null;
1040        private KeyStore ks = null;
1041        private String keystoreFile = "";
1042        private HashMap keyTable = new HashMap();
1043        private boolean cacheKeys = true;
1044        private String pkpass = null;
1045
1046        public void init(Properties properties) throws Exception
1047        {
1048            String keyfile = properties.getProperty("keystorefile");
1049            if(keyfile != null)
1050            {
1051                String ktype = properties.getProperty(
1052                                         SAE_CONFIG_KEYSTORE_TYPE, "JKS");
1053                ks = KeyStore.getInstance(ktype);
1054                FileInputStream fileinputstream = new FileInputStream(keyfile);
1055                String kpass = properties.getProperty(SAE_CONFIG_KEYSTORE_PASS);
1056                pkpass = properties.getProperty(SAE_CONFIG_PRIVATE_KEY_PASS);
1057                ks.load(fileinputstream, kpass.toCharArray());
1058                String pkeyalias = properties.getProperty(
1059                                           SAE_CONFIG_PRIVATE_KEY_ALIAS );
1060                if(pkeyalias != null) {
1061                    privateKey = (PrivateKey)ks.getKey(pkeyalias, 
1062                                           pkpass.toCharArray());
1063                }
1064                String pubkeyalias = properties.getProperty(
1065                                           SAE_CONFIG_PUBLIC_KEY_ALIAS );
1066                if ("false".equals(properties.getProperty(
1067                          SAE_CONFIG_CACHE_KEYS)))
1068                    cacheKeys = false;
1069
1070                if (cacheKeys && pubkeyalias != null)
1071                    getPublicKeyFromKeystore(pubkeyalias);
1072            }
1073        }
1074        public PrivateKey getPrivateKey(String alias)
1075        {
1076            try {
1077                if (alias == null)
1078                    return privateKey;
1079                return (PrivateKey)ks.getKey(alias, 
1080                                pkpass.toCharArray());
1081            } catch (Exception ex) {
1082                return null;
1083            }
1084        }
1085        public X509Certificate getPublicKey(String alias)
1086        {
1087            X509Certificate x509certificate = 
1088                         (X509Certificate)keyTable.get(alias);
1089            if (x509certificate == null && ks != null) {
1090                try
1091                {
1092                    x509certificate = getPublicKeyFromKeystore(alias);
1093                }
1094                catch(Exception exception)
1095                {
1096                    System.out.println("SAE:getPublicKey:Exc:"+exception);
1097                }
1098            }
1099            return x509certificate;
1100        }
1101        public void setPrivatekey(PrivateKey privatekey)
1102        {
1103            privateKey = privatekey;
1104        }
1105        public void addPublicKey(
1106                       String pubkeyalias, X509Certificate x509certificate)
1107        {
1108            keyTable.put(pubkeyalias, x509certificate);
1109        }
1110        private X509Certificate getPublicKeyFromKeystore(String pubkeyalias)
1111            throws Exception
1112        {
1113            X509Certificate x509certificate = 
1114                (X509Certificate)ks.getCertificate(pubkeyalias);
1115            if(cacheKeys)
1116                keyTable.put(pubkeyalias, x509certificate);
1117            return x509certificate;
1118        }
1119    }
1120}