All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.samsung.knoxwsm.util.KnoxTokenUtility Maven / Gradle / Ivy

package com.samsung.knoxwsm.util;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.samsung.knoxwsm.identity.KnoxIdentity;
import com.samsung.knoxwsm.identity.KnoxToken;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.codec.binary.Base64;

import java.io.InputStream;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class KnoxTokenUtility {

    private static final String JSON_PRIVATE_KEY_NAME = "Private";
    private static final String JSON_PUBLIC_KEY_NAME = "Public";
    private static final String JSON_ID_NAME = "Identifier";

    private static final String ALGORITHM = "RSA";


    private KnoxTokenUtility() {
        // do nothing
    }

    /**
     * Builds a clientIdentifier with the Knox Api Access Token as a claim.
     * @return {@link AccessTokenBuilder}
     */
    public static Builder signedAccessTokenBuilder() {
        return new AccessTokenBuilder();
    }

    /**
     * Builds a clientIdentifier with the Knox Client Identifier as a claim.
     * @return {@link ClientIdentifierTokenBuilder}
     */
    public static Builder signedClientIdentifierBuilder() {
        return new ClientIdentifierTokenBuilder();
    }

    public static class Builder {

        private Builder() {
            // do nothing
        }

        private static final int DURATION_TOKEN_VALID = 30;

        private KnoxIdentity knoxIdentity;

        private PrivateKey privateKey;

        private PublicKey publicKey;

        private static final String AUDIENCE = "KnoxWSM";

        private static final String CLAIM_ACCESS_TOKEN = "accessToken";

        private static final String CLAIM_SESSION_TOKEN = "sessionToken";

        private static final String CLAIM_CLIENT_IDENTIFIER = "clientIdentifier";

        private static final String CLAIM_PUBLIC_KEY = "publicKey";

        private static final String CLAIM_DELEGATED_SA_TOKEN= "delegatedSAToken";

        private static final long DURATION_VALID_MILLISECONDS = DURATION_TOKEN_VALID * 60 * 1000;

        private Map claims = new HashMap();

        private long durationValidForInMilliseconds;

        /**
         * @param privateKey The {@link PrivateKey} to be used for signing JWT.
         * @return A Builder object {@link Builder}
         */
        public Builder setSigningKey(PrivateKey privateKey) {
            this.privateKey = privateKey;
            return this;
        }

        /**
         * @param publicKey The {@link PublicKey} that is converted to Base64 encoded String to be sent with access token jwt.
         * @return A Builder object {@link Builder}
         */
        public Builder setPublicKeyBase64EncodedString(PublicKey publicKey) {
            this.publicKey = publicKey;
            this.claims.put(CLAIM_PUBLIC_KEY, Base64.encodeBase64String(publicKey.getEncoded()));
            return this;
        }

        /**
         * @param knoxIdentityInputStream The {@link KnoxIdentity} to be used to extract private key for signing JWT.
         * @return A Builder object {@link Builder}
         */
        public Builder setKnoxIdentity(InputStream knoxIdentityInputStream) {
            this.knoxIdentity = generateKnoxIdentity(knoxIdentityInputStream);
            this.privateKey = knoxIdentity.getKeyPair().getPrivate();
            this.publicKey = knoxIdentity.getKeyPair().getPublic();
            this.claims.put(CLAIM_PUBLIC_KEY, Base64.encodeBase64String(publicKey.getEncoded()));
            return this;
        }

        /**
         * @param token The content to be used to generate JWT.
         * @return A Builder object {@link Builder}
         */
        public Builder setToken(String token) {
            if (this instanceof AccessTokenBuilder) {
                this.claims.put(CLAIM_ACCESS_TOKEN, token);
            } else if (this instanceof ClientIdentifierTokenBuilder) {
                this.claims.put(CLAIM_CLIENT_IDENTIFIER, token);
            }

            return this;
        }

        /**
         * @param idpAccessToken The content to be used to generate JWT.
         * @return A Builder object {@link Builder}
         */
        public Builder setIdpToken(String idpAccessToken) {
           this.claims.put(CLAIM_DELEGATED_SA_TOKEN, idpAccessToken);
           return this;
        }

        /**
         * @param sessionToken The content to be used to generate JWT.
         * @return A Builder object {@link Builder}
         */
        public Builder setSessionToken(String sessionToken) {
            this.claims.put(CLAIM_SESSION_TOKEN, sessionToken);
            return this;
        }

        /**
         * Sets the duration that JWT will be valid for. Value should be greater than or equal to 0 and less than or equal to 30.
         * @param duration The duration in minutes that the JWT should be valid for.
         * @return A Builder object {@link Builder}
         */
        public Builder setValidForMinutes(int duration) {

            if (duration < 0 || duration > 30) {
                throw new IllegalArgumentException("Invalid duration provided. Duration must be >= 0 and <= 30");
            }

            this.durationValidForInMilliseconds = duration * 60 * 1000;

            return this;
        }

        /**
         * @return A signed JWT.
         */
        public String build() {

            if (this.claims == null || this.claims.isEmpty()) {
                throw new IllegalArgumentException("Cannot create empty token. Please provide value for token");
            }

            Date nowDate = new Date();

            return Jwts.builder()
                    .setClaims(this.claims)
                    .setId(UUID.randomUUID().toString() + UUID.randomUUID().toString())
                    .setIssuedAt(nowDate)
                    .setExpiration(new Date(nowDate.getTime() + (durationValidForInMilliseconds == 0 ? DURATION_VALID_MILLISECONDS : durationValidForInMilliseconds)))
                    .setAudience(AUDIENCE)
                    .signWith(SignatureAlgorithm.RS256, this.privateKey)
                    .compact();
        }


        public KnoxToken buildKnoxToken() {

            if (this.claims == null || this.claims.isEmpty()) {
                throw new IllegalArgumentException("Cannot create empty token. Please provide value for token");
            }

            Date nowDate = new Date();

            String jwt = Jwts.builder()
                    .setClaims(this.claims)
                    .setId(UUID.randomUUID().toString() + UUID.randomUUID().toString())
                    .setIssuedAt(nowDate)
                    .setExpiration(new Date(nowDate.getTime() + (durationValidForInMilliseconds == 0 ? DURATION_VALID_MILLISECONDS : durationValidForInMilliseconds)))
                    .setAudience(AUDIENCE)
                    .signWith(SignatureAlgorithm.RS256, this.privateKey)
                    .compact();

            String identifier = this.knoxIdentity != null ? knoxIdentity.getIdentifier() : null;

            return new KnoxToken(identifier, jwt);
        }
    }

    private static class AccessTokenBuilder extends Builder {

    }

    private static class ClientIdentifierTokenBuilder extends Builder {

    }

    /**
     * Generates a JWT containing an access token that is signed with the provided key.
     * @param privateKey {@link PrivateKey} to be used for signing token.
     * @param publicKey (@link PublicKey} to be used for signing token.
     * @param token {@link String} Api Access Token obtained from Knox.
     * @return A signed JWT {@link String}
     */
    public static String generateSignedAccessTokenJWT(PrivateKey privateKey, PublicKey publicKey, String token) {

        if (token == null || token.trim().length() == 0) {
            throw new IllegalArgumentException("Token cannot be null or empty.");
        }

        return KnoxTokenUtility
                .signedAccessTokenBuilder()
                .setSigningKey(privateKey)
                .setPublicKeyBase64EncodedString(publicKey)
                .setToken(token)
                .build();
    }

    /**
     * Generates a JWT containing an access token that is signed with private key within KnoxIdentity.
     * @param knoxIdentity {@link KnoxIdentity} to be used for signing token.
     * @param token {@link String} Api Access Token obtained from Knox.
     * @return A signed JWT {@link String}
     */
    public static String generateSignedAccessTokenJWT(KnoxIdentity knoxIdentity, String token) {

        if (token == null || token.trim().length() == 0) {
            throw new IllegalArgumentException("Token cannot be null or empty.");
        }

        return KnoxTokenUtility
                .signedAccessTokenBuilder()
                .setSigningKey(knoxIdentity.getKeyPair().getPrivate())
                .setPublicKeyBase64EncodedString(knoxIdentity.getKeyPair().getPublic())
                .setToken(token)
                .build();
    }

    /**
     * Generates a JWT containing an access token that is signed with private key within KnoxIdentity.
     * @param knoxIdentityInputStream An instance of InputStream representing the Public/Private keypair.
     * @param token {@link String} Api Access Token obtained from Knox.
     * @return A signed JWT {@link String}
     */
    public static String generateSignedAccessTokenJWT(InputStream knoxIdentityInputStream, String token) {

        if (token == null || token.trim().length() == 0) {
            throw new IllegalArgumentException("Token cannot be null or empty.");
        }

        return KnoxTokenUtility
                .signedAccessTokenBuilder()
                .setKnoxIdentity(knoxIdentityInputStream)
                .setToken(token)
                .build();
    }


    /**
     * Generates a JWT containing an access token that is signed with private key within KnoxIdentity.
     * @param knoxIdentityInputStream {@link InputStream} InputStream object.
     * @param token {@link String} Api Access Token obtained from Knox.
     * @return A signed JWT {@link String}
     */
    public static String generateSignedSessionTokenJWT(InputStream knoxIdentityInputStream, String token) {

        if (token == null || token.trim().length() == 0) {
            throw new IllegalArgumentException("Token cannot be null or empty.");
        }

        return KnoxTokenUtility
                .signedAccessTokenBuilder()
                .setKnoxIdentity(knoxIdentityInputStream)
                .setSessionToken(token)
                .build();
    }

    /**
     * Generates a JWT containing a client identifier that is signed with the provided key.
     * @param privateKey {@link PrivateKey} to be used for signing token.
     * @param token {@link String} Client Identifier obtained from Knox for calling Service apis.
     * @return A signed JWT {@link String}
     */
    public static String generateSignedClientIdentifierJWT(PrivateKey privateKey, String token) {

        if (token == null || token.trim().length() == 0) {
            throw new IllegalArgumentException("Token cannot be null or empty.");
        }

        return KnoxTokenUtility
                .signedClientIdentifierBuilder()
                .setSigningKey(privateKey)
                .setToken(token)
                .build();
    }

    /**
     * Generates a JWT containing a client identifier that is signed with the provided key.
     * @param knoxIdentity {@link KnoxIdentity} to be used for signing token.
     * @param token {@link String} Client Identifier obtained from Knox for calling Service apis.
     * @return A signed JWT {@link String}
     */
    public static String generateSignedClientIdentifierJWT(KnoxIdentity knoxIdentity, String token) {

        if (token == null || token.trim().length() == 0) {
            throw new IllegalArgumentException("Token cannot be null or empty.");
        }

        return KnoxTokenUtility
                .signedClientIdentifierBuilder()
                .setSigningKey(knoxIdentity.getKeyPair().getPrivate())
                .setToken(token)
                .build();
    }

    /**
     * Generates a JWT containing a client identifier that is signed with the provided key.
     * @param knoxIdentityInputStream An instance of InputStream representing the Public/Private keypair.
     * @param token {@link String} Client Identifier obtained from Knox for calling Service apis.
     * @return A signed JWT {@link String}
     */
    public static String generateSignedClientIdentifierJWT(InputStream knoxIdentityInputStream, String token) {

        if (token == null || token.trim().length() == 0) {
            throw new IllegalArgumentException("Token cannot be null or empty.");
        }

        return KnoxTokenUtility
                .signedClientIdentifierBuilder()
                .setKnoxIdentity(knoxIdentityInputStream)
                .setToken(token)
                .build();
    }

    /**
     * Generates a JWT containing a client identifier that is signed with the provided key.
     * @param knoxIdentityInputStream {@link InputStream} InputStream object.
     * @param token {@link String} Client Identifier obtained from Knox for calling Service apis.
     * @param idpAccessToken {@link String}
     * @return A signed JWT {@link String}
     */
    public static String generateSignedClientIdentifierJWTWithIdpAccessToken(InputStream knoxIdentityInputStream, String token,
                                                                                               String idpAccessToken) {

        if (token == null || token.trim().length() == 0) {
            throw new IllegalArgumentException("Token cannot be null or empty.");
        }

        if (idpAccessToken == null || idpAccessToken.trim().length() == 0) {
            throw new IllegalArgumentException("UserId cannot be null or empty.");
        }

        return KnoxTokenUtility
                .signedClientIdentifierBuilder()
                .setKnoxIdentity(knoxIdentityInputStream)
                .setToken(token)
                .setIdpToken(idpAccessToken)
                .build();
    }

    /**
     * Generates a Base64 encoded String of your PublicKey.
     * @param knoxIdentityInputStream The input stream of your Knox Certificate (certificate.json)
     * @return String Base64 encoded String for PublicKey present in the provided Knox Certificate
     */
    public static String generateBase64EncodedStringPublicKey(InputStream knoxIdentityInputStream) {
        return Base64.encodeBase64String(KnoxTokenUtility.generateKnoxIdentity(knoxIdentityInputStream)
                .getKeyPair()
                .getPublic().getEncoded());
    }

    /**
     * A Utility that helps generate an instance of {@link PrivateKey} from a PKCS#8 format Base64 encoded String.
     * @param pkcs8FormatStringPrivateKeyEncoded A Base64 encoded String representing the private key.
     * @return An instance of {@link PrivateKey}
     */
    private static PrivateKey createPrivateKey(String pkcs8FormatStringPrivateKeyEncoded) {

        if (pkcs8FormatStringPrivateKeyEncoded == null || pkcs8FormatStringPrivateKeyEncoded.length() == 0) {
            throw new IllegalArgumentException("Cannot create private key from empty string");
        }

        try {
            byte[] bytes = Base64.decodeBase64(pkcs8FormatStringPrivateKeyEncoded);

            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);

            KeyFactory keyFactory = KeyFactory.getInstance("RSA");

            return keyFactory.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Could not construct private key");
        } catch (InvalidKeySpecException e) {
            throw new IllegalArgumentException("Could not construct private key");
        }
    }

    static KnoxIdentity generateKnoxIdentity(InputStream inputStream) {

        Key privateKey = null;
        Key publicKey = null;
        String identifier = null;

        try {
            JsonNode jsonNode = new ObjectMapper().readTree(inputStream);
            privateKey = constructKey(JSON_PRIVATE_KEY_NAME, jsonNode.get(JSON_PRIVATE_KEY_NAME).asText());
            publicKey = constructKey(JSON_PUBLIC_KEY_NAME, jsonNode.get(JSON_PUBLIC_KEY_NAME).asText());

        } catch (Exception e) {
            throw new RuntimeException("Could not read contents of file.", e);
        }

        return new KnoxIdentity(new KeyPair((PublicKey) publicKey, (PrivateKey) privateKey), identifier);
    }

    private static Key constructKey(String keyType, String value) {

        Key key = null;

        try {
            if (JSON_PRIVATE_KEY_NAME.equals(keyType)) {
                byte[] bytes = Base64.decodeBase64(value);

                PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);

                KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);

                key = keyFactory.generatePrivate(keySpec);
            } else if (JSON_PUBLIC_KEY_NAME.equals(keyType)) {
                X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(value));

                key = KeyFactory.getInstance(ALGORITHM).generatePublic(x509EncodedKeySpec);
            } else {
                throw new RuntimeException();
            }
        } catch (Exception e) {
            throw new RuntimeException("Could not construct KeyPair from provided File", e);
        }

        return key;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy