com.orange.oidc.tim.service.SDCardStorage.java Source code

Java tutorial

Introduction

Here is the source code for com.orange.oidc.tim.service.SDCardStorage.java

Source

/*
* 
* Copyright (C) 2015 Orange Labs
* 
* 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.
* 
*/

package com.orange.oidc.tim.service;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.TimeZone;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.os.Environment;
import android.util.Base64;
import android.util.Log;

/**
 * 
 * SDCardStorage class
 * used to generate crypted and signed request objects
 * and to store tokens and generate tim access tokens
 * on phone SD card storage, path is to be defined
 *
 */
public class SDCardStorage extends TimSecureStorage {

    protected static final String TAG = "SD Storage";

    static private final String SAVE_DIR = "/openid-connect";
    static private final String SAVE_FILE = "TIM.data_php.txt";

    // final static String alg = "SHA1withRSA";
    final static String alg = "RS256";

    // JWS signature header
    static final String signHeader = "{\"alg\":\"" + alg + "\",\"kid\":\"k2bdc\"}";

    static final int TIM_ACCESS_TOKEN_TIMEOUT = 180;

    // client_id and client_secret
    // here for demo only, must be stored in a secure element
    static final private String TIM_client_id = "TIM";
    static final private String TIM_secret = "timsecret";

    static final private String TIM_redirect_uri = "http://tim/";

    public String getClientId() {
        return TIM_client_id;
    }

    public String getRedirectUri() {
        return TIM_redirect_uri;
    }

    // local class used for storing params and tokens
    static class RequestTokenData {
        String lastVerifTime;
        String security_level;
        String server_url;
        String client_id;
        String scope;
        String serverScope;
        String redirect_uri;
        String id_token;
        String refresh_token;
        String expires;
        String jwk;

        public RequestTokenData clone() {
            RequestTokenData rt = new RequestTokenData();
            rt.lastVerifTime = lastVerifTime;
            rt.security_level = security_level;
            rt.server_url = server_url;
            rt.client_id = client_id;
            rt.scope = scope;
            rt.serverScope = serverScope;
            rt.redirect_uri = redirect_uri;
            rt.id_token = id_token;
            rt.refresh_token = refresh_token;
            rt.expires = expires;
            rt.jwk = jwk;

            return rt;
        }

        public boolean read(BufferedReader reader) {
            try {
                lastVerifTime = reader.readLine();
                security_level = reader.readLine();
                server_url = reader.readLine();
                if (server_url == null)
                    return false;
                client_id = reader.readLine();
                scope = reader.readLine();
                serverScope = reader.readLine();
                redirect_uri = reader.readLine();
                id_token = reader.readLine();
                refresh_token = reader.readLine();
                expires = reader.readLine();
                jwk = reader.readLine();

                return true;

            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }

        public boolean write(BufferedWriter writer) {
            try {
                write(lastVerifTime, writer);
                write(security_level, writer);
                write(server_url, writer);
                write(client_id, writer);
                write(scope, writer);
                write(serverScope, writer);
                write(redirect_uri, writer);
                write(id_token, writer);
                write(refresh_token, writer);
                write(expires, writer);
                write(jwk, writer);

                return true;

            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }

        private void write(String s, BufferedWriter writer) throws IOException {
            if (s != null && s.length() > 0) {
                writer.append(s);
            }
            writer.newLine();
        }

        public RSAPrivateKey privKey() {
            if (jwk != null && jwk.length() > 0) {
                return KryptoUtils.privKeyFromJwk(jwk);
            }
            return null;
        }

        public PublicKey pubKey() {
            if (jwk != null && jwk.length() > 0) {
                return KryptoUtils.pubKeyFromJwk(jwk);
            }
            return null;
        }

        public String jwkPubKey() {
            if (jwk != null && jwk.length() > 0) {

                try {
                    JSONObject jkw = new JSONObject(jwk).getJSONArray("keys").getJSONObject(0);
                    JSONObject jk = new JSONObject();
                    jk.put("kty", "RSA");

                    jk.put("kid", KryptoUtils.kidFromJwk(jwk));
                    jk.put("e", jkw.getString("e"));

                    jk.put("n", jkw.getString("n"));
                    JSONArray ja = new JSONArray();
                    ja.put(jk);
                    JSONObject jo = new JSONObject();
                    jo.put("keys", ja);
                    return jo.toString();
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }

        public boolean isExpired() {
            if (expires != null && expires.length() > 0) {
                long expireL = 0;
                try {
                    expireL = Long.parseLong(expires);
                } catch (Exception e) {
                }
                Calendar expCal = Calendar.getInstance();
                expCal.setTimeInMillis(expireL * 1000);
                return expCal.before(Calendar.getInstance());
            }
            return true;
        }
    }

    // return true if equals content / null / empty ), false otherwise
    static boolean compareStrings(String a, String b) {
        if (a == null && b == null)
            return true;
        if (a == null && b != null && b.length() == 0)
            return true;
        if (b == null && a != null && a.length() == 0)
            return true;
        try {
            return a.compareTo(b) == 0;
        } catch (Exception e) {
        }
        return false;
    }

    // local class used for managing all tokens/params 
    static class RequestTokens {
        List<RequestTokenData> rtDatas = new ArrayList<RequestTokenData>();

        // private function to find token according to parameters
        protected RequestTokenData get(String server_url, String client_id, String scope, String redirect_uri) {
            for (RequestTokenData rt : rtDatas) {
                // Logd(TAG,"RequestTokens get : server_url   "+server_url+" compare with "+ rt.server_url);
                // Logd(TAG,"RequestTokens get : client_id    "+client_id+" compare with "+ rt.client_id);
                // Logd(TAG,"RequestTokens get : scope        "+scope+" compare with "+ rt.scope);
                // Logd(TAG,"RequestTokens get : redirect_uri "+redirect_uri+" compare with "+ rt.redirect_uri);
                if (rt != null && compareStrings(rt.server_url, server_url)
                        && compareStrings(rt.client_id, client_id) && compareStrings(rt.scope, scope)
                // && compareStrings( rt.redirect_uri, redirect_uri )
                ) {
                    return rt;
                }
            }

            return null;
        }

        // static function find token in local storage
        static public RequestTokenData find(String server_url, String client_id, String scope,
                String redirect_uri) {
            RequestTokens rts = new RequestTokens();
            rts.load();
            return rts.get(server_url, client_id, scope, redirect_uri);
        }

        // update tokens
        static public void update_tokens(OpenidConnectParams ocp, String id_token, String refresh_token,
                String expires, String serverScope) {
            RequestTokens rts = new RequestTokens();
            rts.load();
            RequestTokenData rt = rts.get(ocp.m_server_url, ocp.m_client_id, ocp.m_scope, ocp.m_redirect_uri);
            if (rt != null) {
                rt.id_token = id_token;
                rt.refresh_token = refresh_token;
                rt.expires = expires;
                rt.serverScope = serverScope;
                rts.save();
            } else {
                Logd(TAG, "trying to update token not found");
            }
        }

        static public boolean deleteTokens(String server_url, String client_id, String scope, String redirect_uri) {
            RequestTokens rts = new RequestTokens();
            rts.load();
            RequestTokenData rt = rts.get(server_url, client_id, scope, redirect_uri);
            if (rt != null) {
                rts.rtDatas.remove(rt);
                rts.save();
                return true;
            }
            return false;
        }

        static public void genTimAppKey(String server_url, String client_id, String scope, String redirect_uri) {
            RequestTokens rts = new RequestTokens();
            rts.load();
            RequestTokenData rt = rts.get(server_url, client_id, scope, redirect_uri);
            if (rt != null) {
                // generate new keypair
                KeyPair kp = KryptoUtils.generateRsaKeyPair(512);
                if (kp != null) {
                    rt.jwk = KryptoUtils.getJwkPrivate(kp);
                    rts.save();
                }
            }
        }

        static public void save_tokens(OpenidConnectParams ocp, String id_token, String refresh_token,
                String expires, String serverScope) {
            RequestTokens rts = new RequestTokens();
            rts.load();
            RequestTokenData rt = rts.get(ocp.m_server_url, ocp.m_client_id, ocp.m_scope, ocp.m_redirect_uri);

            // no use for demo
            /*
            if ( rt != null ) {
               // should not come here ...
               return;
            }
            */
            if (rt == null) {
                rt = new RequestTokenData();
                rt.server_url = ocp.m_server_url;
                rt.client_id = ocp.m_client_id;
                rt.scope = ocp.m_scope;
                rt.redirect_uri = ocp.m_redirect_uri;
                rts.rtDatas.add(rt);
            }

            rt.serverScope = serverScope;
            rt.id_token = id_token;
            rt.refresh_token = refresh_token;
            rt.expires = expires;

            Logd("SDStorage.save_tokens", rt.refresh_token);

            rts.save();
        }

        static public String getNewTimToken(OpenidConnectParams ocp) {

            if (ocp == null)
                return null;

            RequestTokens rts = new RequestTokens();
            rts.load();
            RequestTokenData rt = rts.get(ocp.m_server_url, ocp.m_client_id, ocp.m_scope, ocp.m_redirect_uri);
            if (rt != null) {
                // check expiration
                if (!rt.isExpired()) {
                    Token token = new Token(rt.id_token);
                    Logd(TAG, "Token not expired");
                    // generate new TIM access token
                    String tim_access_token = buildTimAccessToken(ocp.m_client_id, token.sub, rt.scope, ocp.m_jwk);
                    Logd(TAG, "built tim access token" + tim_access_token);
                    String dataToSign = null;
                    try {
                        dataToSign = KryptoUtils.encodeB64(tim_access_token.getBytes("UTF-8"));
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }

                    Logd(TAG, "Get crypto keys from jwk " + rt.jwk);
                    RSAPrivateKey privKey = KryptoUtils.privKeyFromJwk(rt.jwk);
                    PublicKey pubKey = KryptoUtils.pubKeyFromJwk(rt.jwk);

                    // sign
                    // String signH = "{\"alg\":\""+alg+"\",\"kid\":\"k2bdc\"}";
                    String signH = "{\"alg\":\"" + alg + "\",\"kid\":\"" + KryptoUtils.kidFromJwk(rt.jwk) + "\"}";
                    String signed = KryptoUtils.signJWS(dataToSign, signH, alg, privKey);
                    try {
                        KryptoUtils.verifyJWS(signed, alg, pubKey, privKey);
                    } catch (Exception e) {
                    }
                    return signed;
                }
                Logd(TAG, "Token expired");
            }
            return null;
        }

        private boolean load() {
            rtDatas.clear();
            String p = getSdFilePath();
            if (p == null)
                return false;

            File file = new File(p);

            try {
                BufferedReader reader = new BufferedReader(
                        new InputStreamReader(new FileInputStream(file), "UTF-8"));
                RequestTokenData rt = new RequestTokenData();
                while (rt.read(reader)) {
                    rtDatas.add(rt);
                    rt = new RequestTokenData();
                }
                reader.close();

            } catch (FileNotFoundException fnfe) {
                Logerr(TAG, "NO TOKEN FILE");
            } catch (Exception e) {
                e.printStackTrace();
            }

            return true;
        }

        private boolean save() {
            String p = getSdFilePath();
            if (p == null)
                return false;

            File file = new File(p);

            try {
                BufferedWriter writer = new BufferedWriter(
                        new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
                for (int i = 0; i < rtDatas.size(); i++) {
                    RequestTokenData rt = rtDatas.get(i);
                    rt.write(writer);
                }
                writer.flush();
                writer.close();

            } catch (Exception e) {
                Logd(TAG, "RequestTokens.save error");
                e.printStackTrace();
            }

            return true;
        }

        String getSdFilePath() {

            String path = null;
            String sdPath = Environment.getExternalStorageDirectory().toString();

            try {
                File f = new File(sdPath);
                if (f.exists()) {
                    File dir = new File(f.getAbsolutePath() + SAVE_DIR);
                    if (!dir.exists()) {
                        dir.mkdir();
                    }
                    if (dir.exists()) {
                        path = dir.getAbsolutePath();
                        if (!path.endsWith("/")) {
                            path += "/";
                        }
                        path += SAVE_FILE;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

            return path;
        }
    }

    // save tokens ( id and refresh ) to corresponding request 
    public String save_tokens(OpenidConnectParams ocp, String id_token, String refresh_token, String expires_in) {

        if (ocp != null) {

            int expires = 0;
            try {
                expires = Integer.parseInt(expires_in);
            } catch (Exception e) {
            }

            Calendar cal = Calendar.getInstance();
            cal.setTimeZone(TimeZone.getTimeZone("GMT"));
            long timems = cal.getTimeInMillis() + 1000 * expires;
            RequestTokens.save_tokens(ocp, id_token, refresh_token, "" + (timems / 1000), ocp.m_server_scope);
            return "";
        }
        return null;
    }

    // update tokens to specified app request
    public String update_tokens(OpenidConnectParams ocp, String id_token, String refresh_token, String expires) {
        if (ocp != null) {
            RequestTokens.update_tokens(ocp, id_token, refresh_token, expires, ocp.m_server_scope);
            return "";
        }
        return null;
    }

    // search for tokens, and if found return them
    public TokensKeys read_tokens(String server_url, String client_id, String scope) {

        RequestTokenData rt = RequestTokens.find(server_url, client_id, scope, null);
        if (rt != null) {
            TokensKeys tk = new TokensKeys();
            tk.id_token = rt.id_token;
            tk.refresh_token = rt.refresh_token;
            tk.expires = rt.expires;
            tk.privKey = rt.privKey();
            tk.pubKey = (RSAPublicKey) rt.pubKey();
            tk.jwkPub = rt.jwkPubKey();
            tk.serverScope = rt.serverScope;
            return tk;
        }

        return null;
    }

    // get request object 
    public String getTimRequestObject(String server_url, String client_id, String scope, PublicKey serverPubKey) {

        JSONObject object = new JSONObject();
        try {
            object.put("response_type", "code");
            object.put("scope", scope);
            object.put("redirect_uri", getRedirectUri());

            object.put("client_id", TIM_client_id);

            JSONObject timJS = new JSONObject();
            timJS.put("app_id", new JSONObject().put("value", client_id));
            timJS.put("tim_app_key", new JSONObject().put("essential", true));
            object.put("tim", timJS);

            Logd(TAG, "getTimRequestObject : " + object.toString());

            String requestParam64 = KryptoUtils.encodeB64(object.toString().getBytes());

            // get JWT
            String jwS = KryptoUtils.signJWS(requestParam64, signHeader, alg, RsaKeyTim.privRsaKey);
            // jwS = object.toString();
            byte jwsBytes[] = jwS.getBytes();
            short paddLeft = (short) (jwsBytes.length % 16);
            if (paddLeft > 0) {
                byte padd = (byte) (16 - paddLeft);
                byte jwsBytes_tmp[] = new byte[jwsBytes.length + padd];
                System.arraycopy(jwsBytes, 0, jwsBytes_tmp, 0, jwsBytes.length);
                for (byte i = 0; i < padd; i++) {
                    jwsBytes_tmp[(short) (jwsBytes.length + i)] = padd;
                }
                jwsBytes = jwsBytes_tmp;
            }

            // encrypt JWT request by JWE
            return KryptoUtils.encryptJWE(jwsBytes, serverPubKey, null);

        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    // request  to generate new TIM app keys
    // and return tokens and new public key
    public TokensKeys genTimAppKey(OpenidConnectParams ocp) {
        RequestTokens.genTimAppKey(ocp.m_server_url, ocp.m_client_id, ocp.m_scope, ocp.m_redirect_uri);
        return read_tokens(ocp.m_server_url, ocp.m_client_id, ocp.m_scope);
    }

    public String getPrivateKeyJwt(String token_endpoint) {
        String privateKeyJwt = null;
        try {
            JSONObject jo = new JSONObject();
            jo.put("iss", TIM_client_id);
            jo.put("sub", TIM_client_id);
            jo.put("aud", token_endpoint);
            jo.put("jti", new BigInteger(130, new SecureRandom()).toString(32));
            long now = Calendar.getInstance().getTimeInMillis() / 1000;
            // expires in 3 minutes
            jo.put("exp", "" + (now + 180));

            String dataToSign = null;
            try {
                dataToSign = KryptoUtils.encodeB64(jo.toString().getBytes("UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }

            if (dataToSign != null && dataToSign.length() > 0) {
                // sign with TIM private key
                String signH = "{\"alg\":\"" + alg + "\"}";
                privateKeyJwt = KryptoUtils.signJWS(dataToSign, signH, alg, RsaKeyTim.privRsaKey);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        return privateKeyJwt;
    }

    public String getClientSecretBasic() {

        String bearer = (TIM_client_id + ":" + TIM_secret);
        return Base64.encodeToString(bearer.getBytes(), Base64.DEFAULT);
    }

    // build a json string to represent a tim access token
    public static String buildTimAccessToken(String client_id, String sub, String scope, String jwk) {
        // android.os.Debug.waitForDebugger();
        JSONObject jo = new JSONObject();
        try {

            String tim_id = TIM_client_id;

            // add subject
            jo.put("sub", sub);

            // prepare and add audience
            JSONArray ja = new JSONArray();
            ja.put(tim_id);
            ja.put(client_id);
            jo.put("aud", ja);

            // add scope
            jo.put("scope", scope);

            // add times : delivery and expiration
            long now = Calendar.getInstance().getTimeInMillis() / 1000;
            jo.put("exp", "" + (now + TIM_ACCESS_TOKEN_TIMEOUT));
            jo.put("iat", "" + now);
            // jo.put("auth_time", ""+now);

            // add authorization party
            jo.put("azp", tim_id);

            // add jwk if specified
            if (jwk != null && jwk.length() > 0) {
                jo.put("jwk", jwk);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return jo.toString();
    }

    // generate a new signed TIM access token
    public String getNewTimToken(OpenidConnectParams ocp) {
        return RequestTokens.getNewTimToken(ocp);
    }

    public boolean delete_tokens(String serverUrl, String client_id, String scope) {
        return RequestTokens.deleteTokens(serverUrl, client_id, scope, null);
    }

    static void Logd(String tag, String msg) {
        if (tag == null)
            tag = "unknown";
        if (msg == null)
            msg = "unknown";
        Log.d(tag, msg);
    }

    static void Logerr(String tag, String msg) {
        if (tag == null)
            tag = "unknown";
        if (msg == null)
            msg = "unknown";
        Log.e(tag, msg);
    }

}