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