/* * JBoss, Home of Professional Open Source * * Copyright 2013 Red Hat, Inc. and/or its affiliates. * * 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 org.picketlink.json.jose; import static org.picketlink.json.JsonConstants.COMMON.KEY_ID; import static org.picketlink.json.JsonConstants.JWK.KEY_ALGORITHM; import static org.picketlink.json.JsonConstants.JWK.KEY_OPERATIONS; import static org.picketlink.json.JsonConstants.JWK.KEY_TYPE; import static org.picketlink.json.JsonConstants.JWK.KEY_USE; import static org.picketlink.json.JsonConstants.JWK.X509_CERTIFICATE_CHAIN; import static org.picketlink.json.JsonConstants.JWK.X509_CERTIFICATE_SHA1_THUMBPRINT; import static org.picketlink.json.JsonConstants.JWK.X509_CERTIFICATE_SHA256_THUMBPRINT; import static org.picketlink.json.JsonConstants.JWK.X509_URL; import static org.picketlink.json.JsonConstants.JWK_RSA.CRT_COEFFICIENT; import static org.picketlink.json.JsonConstants.JWK_RSA.MODULUS; import static org.picketlink.json.JsonConstants.JWK_RSA.PRIME_EXPONENT_P; import static org.picketlink.json.JsonConstants.JWK_RSA.PRIME_EXPONENT_Q; import static org.picketlink.json.JsonConstants.JWK_RSA.PRIME_P; import static org.picketlink.json.JsonConstants.JWK_RSA.PRIME_Q; import static org.picketlink.json.JsonConstants.JWK_RSA.PRIVATE_EXPONENT; import static org.picketlink.json.JsonConstants.JWK_RSA.PUBLIC_EXPONENT; import static org.picketlink.json.JsonMessages.MESSAGES; import static org.picketlink.json.util.Base64Util.b64Decode; import static org.picketlink.json.util.Base64Util.b64Encode; import java.io.ByteArrayInputStream; import java.lang.reflect.Constructor; import java.math.BigInteger; import javax.json.Json; import javax.json.JsonArrayBuilder; import javax.json.JsonObject; import javax.json.JsonObjectBuilder; /** * The base class for building JSON Web Keys (JWKs) with desired key parameters. * * <p> * The following JSON object members are common to all JWK types: * * <ul> * <li>{@link #keyType(String) kty} (required) * <li>{@link #keyUse(String) use} (optional) * <li>{@link #keyOperations(String...) key_ops} (optional) * <li>{@link #keyAlgorithm(String) alg} (optional) * <li>{@link #keyIdentifier(String) kid} (required) * <li>{@link #X509Url(String) x5u} (optional) * <li>{@link #X509CertificateChain(String...) x5c} (optional) * <li>{@link #X509CertificateSHA1Thumbprint(String) x5t} (optional) * <li>{@link #X509CertificateSHA256Thumbprint(String) x5t#S256} (optional) * </ul> * * @param <T> the generic type * @param <B> the generic type * * @author Giriraj Sharma */ public class JWKBuilder<T extends JWK, B extends JWKBuilder<?, ?>> { private final JsonObjectBuilder keyParametersBuilder; private final Class<T> tokenType; /** * Instantiates a new JWK builder. */ public JWKBuilder() { this((Class<T>) JWK.class); } /** * Instantiates a new JWK builder. * * @param tokenTypeoken type */ protected JWKBuilder(Class<T> tokenType) { this.tokenType = tokenType; this.keyParametersBuilder = Json.createObjectBuilder(); } /** * Gets token type. * * @return token type */ protected Class<T> getTokenType() { return this.tokenType; } /** * Gets the key parameters builder. * * @return the key parameters builder */ protected JsonObjectBuilder getkeyParametersBuilder() { return this.keyParametersBuilder; } /** * The kty (key type) member identifies the cryptographic algorithm family used with the key. kty values should either be * registered in the IANA JSON Web Key Types registry defined in [JWA] or be a value that contains a Collision-Resistant * Name. The kty value is a case-sensitive string. This member MUST be present in a JWK. * * @param type * @return */ public JWKBuilder<T, B> keyType(String type) { keyParameter(KEY_TYPE, type); return this; } /** * The use (public key use) member identifies the intended use of the public key. The use parameter is intended for use * cases in which it is useful to distinguish between public signing keys and public encryption keys. * * Values defined by this specification are: * <ul> * <li>sig (signature)</li> * <li>enc (encryption)</li> * </ul> * * Other values MAY be used. Public Key Use values can be registered in the IANA JSON Web Key Use registry defined in * Section 8.2. The use value is a case-sensitive string. Use of the use member is OPTIONAL, unless the application requires * its presence. * * <p> * * @param use the key use * @return */ public JWKBuilder<T, B> keyUse(String use) { keyParameter(KEY_USE, use); return this; } /** * The key_ops (key operations) member identifies the operation(s) that the key is intended to be used for. The key_ops * parameter is intended for use cases in which public, private, or symmetric keys may be present. * * Its value is an array of key operation values. Values defined by this specification are: * * <ul> * <li>sign (compute signature or MAC)</li> * <li>verify (verify signature or MAC)</li> * <li>encrypt (encrypt content)</li> * <li>decrypt (decrypt content and validate decryption, if applicable)</li> * <li>wrapKey (encrypt key)</li> * <li>unwrapKey (decrypt key and validate decryption, if applicable)</li> * <li>deriveKey (derive key)</li> * <li>deriveBits (derive bits not to be used as a key).</li> * </ul> * * <p> * Other values MAY be used. Key operation values can be registered in the IANA JSON Web Key Operations registry defined in * Section 8.3. The key operation values are case-sensitive strings. Duplicate key operation values MUST NOT be present in * the array. * * <p> * Multiple unrelated key operations SHOULD NOT be specified for a key because of the potential vulnerabilities associated * with using the same key with multiple algorithms. Thus, the combinations sign with verify, encrypt with decrypt, and * wrapKey with unwrapKey are permitted, but other combinations SHOULD NOT be used.The use and key_ops JWK members SHOULD * NOT be used together. * * @param keyOperations the key operations * @return */ public JWKBuilder<T, B> keyOperations(String... keyOperations) { if (keyOperations.length == 1) { keyParameter(KEY_OPERATIONS, keyOperations[0]); } else if (keyOperations.length > 1) { JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); for (String operation : keyOperations) { arrayBuilder.add(operation); } this.keyParametersBuilder.add(KEY_OPERATIONS, arrayBuilder); } return this; } /** * The alg (algorithm) member identifies the algorithm intended for use with the key. The values used should either be * registered in the IANA JSON Web Signature and Encryption Algorithms registry defined in [JWA] or be a value that contains * a Collision-Resistant Name. Use of this member is OPTIONAL. * * @param algorithm the algorithm * @return */ public JWKBuilder<T, B> keyAlgorithm(String algorithm) { keyParameter(KEY_ALGORITHM, algorithm); return this; } /** * The kid (key ID) member can be used to match a specific key. This can be used, for instance, to choose among a set of * keys within a JWK Set during key rollover. The structure of the kid value is unspecified. When kid values are used within * a JWK Set, different keys within the JWK Set SHOULD use distinct kid values. (One example in which different keys might * use the same kid value is if they have different kty (key type) values but are considered to be equivalent alternatives * by the application using them.) The kid value is a case-sensitive string. Use of this member is OPTIONAL. * * <p> * When used with JWS or JWE, the kid value is used to match a JWS or JWE kid Header Parameter value. * * @param identifier the identifier * @return */ public JWKBuilder<T, B> keyIdentifier(String identifier) { keyParameter(KEY_ID, identifier); return this; } /** * The x5u (X.509 URL) member is a URI [RFC3986] that refers to a resource for an X.509 public key certificate or * certificate chain [RFC5280]. The identified resource MUST provide a representation of the certificate or certificate * chain that conforms to RFC 5280 [RFC5280] in PEM encoded form [RFC1421]. The key in the first certificate MUST match the * public key represented by other members of the JWK. The protocol used to acquire the resource MUST provide integrity * protection; an HTTP GET request to retrieve the certificate MUST use TLS [RFC2818, RFC5246]; the identity of the server * MUST be validated, as per Section 6 of RFC 6125 [RFC6125]. Use of this member is OPTIONAL. * * @param url the url * @return */ public JWKBuilder<T, B> X509Url(String url) { keyParameter(X509_URL, url); return this; } /** * The x5c (X.509 Certificate Chain) member contains a chain of one or more PKIX certificates [RFC5280]. The certificate * chain is represented as a JSON array of certificate value strings. Each string in the array is a base64 encoded * ([RFC4648] Section 4 -- not base64url encoded) DER [ITU.X690.1994] PKIX certificate value. The PKIX certificate * containing the key value MUST be the first certificate. This MAY be followed by additional certificates, with each * subsequent certificate being the one used to certify the previous one. The key in the first certificate MUST match the * public key represented by other members of the JWK. Use of this member is OPTIONAL. * * @param X509CertificateChain the x509 certificate chain * @return */ public JWKBuilder<T, B> X509CertificateChain(String... X509CertificateChain) { if (X509CertificateChain.length == 1) { keyParameter(X509_CERTIFICATE_CHAIN, X509CertificateChain[0]); } else if (X509CertificateChain.length > 1) { JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); for (String certificate : X509CertificateChain) { arrayBuilder.add(certificate); } this.keyParametersBuilder.add(X509_CERTIFICATE_CHAIN, arrayBuilder); } return this; } /** * The x5t (X.509 Certificate SHA-1 Thumbprint) member is a base64url encoded SHA-1 thumbprint (a.k.a. digest) of the DER * encoding of an X.509 certificate [RFC5280]. The key in the certificate MUST match the public key represented by other * members of the JWK. Use of this member is OPTIONAL. * * @param sha1Thumbprint the SHA1 thumbprint * @return */ public JWKBuilder<T, B> X509CertificateSHA1Thumbprint(String sha1Thumbprint) { keyParameter(X509_CERTIFICATE_SHA1_THUMBPRINT, sha1Thumbprint); return this; } /** * The x5t#S256 (X.509 Certificate SHA-256 Thumbprint) member is a base64url encoded SHA-256 thumbprint (a.k.a. digest) of * the DER encoding of an X.509 certificate [RFC5280]. The key in the certificate MUST match the public key represented by * other members of the JWK. Use of this member is OPTIONAL. * * @param sha256Thumbprint the SHA256 thumbprint * @return */ public JWKBuilder<T, B> X509CertificateSHA256Thumbprint(String sha256Thumbprint) { keyParameter(X509_CERTIFICATE_SHA256_THUMBPRINT, sha256Thumbprint); return this; } /** * Sets the modulus value for the RSA key. * * @param modulus the modulus * @return */ public JWKBuilder<T, B> modulus(BigInteger modulus) { keyParameter(MODULUS, b64Encode(modulus.toByteArray())); return this; } /** * Sets the public exponent of the RSA key. * * @param publicExponent the public exponent * @return */ public JWKBuilder<T, B> publicExponent(BigInteger publicExponent) { keyParameter(PUBLIC_EXPONENT, b64Encode(publicExponent.toByteArray())); return this; } /** * Sets the private exponent of the RSA key. * * @param privateExponent the private exponent * @return */ public JWKBuilder<T, B> privateExponent(BigInteger privateExponent) { keyParameter(PRIVATE_EXPONENT, b64Encode(privateExponent.toByteArray())); return this; } /** * Sets the first prime factor of the private RSA key. * * @param primeP the prime p * @return */ public JWKBuilder<T, B> primeP(BigInteger primeP) { keyParameter(PRIME_P, b64Encode(primeP.toByteArray())); return this; } /** * Sets second prime factor of the private RSA key. * * @param primeQ the prime q * @return */ public JWKBuilder<T, B> primeQ(BigInteger primeQ) { keyParameter(PRIME_Q, b64Encode(primeQ.toByteArray())); return this; } /** * Sets the first factor Chinese Remainder Theorem exponent of the private RSA key. * * @param primeExponentP the prime exponent p * @return */ public JWKBuilder<T, B> primeExponentP(BigInteger primeExponentP) { keyParameter(PRIME_EXPONENT_P, b64Encode(primeExponentP.toByteArray())); return this; } /** * Sets the second factor Chinese Remainder Theorem exponent of the private RSA key. * * @param primeExponentQ the prime exponent q * @return */ public JWKBuilder<T, B> primeExponentQ(BigInteger primeExponentQ) { keyParameter(PRIME_EXPONENT_Q, b64Encode(primeExponentQ.toByteArray())); return this; } /** * Sets the The first Chinese Remainder Theorem coefficient of the private RSA key. * * @param crtCoefficient the CRT coefficient * @return */ public JWKBuilder<T, B> crtCoefficient(BigInteger crtCoefficient) { keyParameter(CRT_COEFFICIENT, b64Encode(crtCoefficient.toByteArray())); return this; } /** * Sets the Key parameter. * * @param name the name of key parameter * @param value the value(s) of key parameter * @return */ public JWKBuilder<T, B> keyParameter(String name, String... value) { setString(this.keyParametersBuilder, name, value); return this; } /** * Updates the {@link javax.json.JsonObjectBuilder} with specified key parameter and its value(s). * * @param builderuilder * @param name the name * @param values the values * @return */ private JWKBuilder<T, B> setString(JsonObjectBuilder builder, String name, String... values) { if (values.length == 1) { builder.add(name, values[0]); } else if (values.length > 1) { JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); for (String value : values) { arrayBuilder.add(value.toString()); } builder.add(name, arrayBuilder); } return this; } /** * Builds {@link javax.json.JsonObjectBuilder} of key parameters. * * @return */ public T build() { return build(this.keyParametersBuilder.build()); } /** * Builds the String JSON. * * @param json the jwk encoded json string * @return */ public T build(String json) { byte[] keyParameters = b64Decode(json); return build(Json.createReader(new ByteArrayInputStream(keyParameters)).readObject()); } /** * Builds the key {@link javax.json.JsonObject}. * * @param keyParametersObject the key parameters object * @return */ protected T build(JsonObject keyParametersObject) { try { Constructor<T> constructor = this.tokenType.getDeclaredConstructor(JsonObject.class); constructor.setAccessible(true); return constructor.newInstance(keyParametersObject); } catch (Exception e) { throw MESSAGES.couldNotCreateToken(this.tokenType, e); } } }