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;
}
}