/*
* 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.ALG;
import static org.picketlink.json.JsonConstants.COMMON.ENC;
import static org.picketlink.json.JsonConstants.COMMON.HEADER_CONTENT_TYPE;
import static org.picketlink.json.JsonConstants.COMMON.HEADER_JSON_WEB_KEY;
import static org.picketlink.json.JsonConstants.COMMON.HEADER_JWK_SET_URL;
import static org.picketlink.json.JsonConstants.COMMON.HEADER_TYPE;
import static org.picketlink.json.JsonConstants.COMMON.KEY_ID;
import static org.picketlink.json.JsonConstants.JWE.CEK_BITLENGTH;
import static org.picketlink.json.JsonConstants.JWE.COMPRESSION_ALG;
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.JsonMessages.MESSAGES;
import static org.picketlink.json.util.Base64Util.b64Decode;
import java.io.ByteArrayInputStream;
import java.lang.reflect.Constructor;
import java.util.Iterator;
import java.util.List;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
/**
* JSON Web Encryption (JWE) header Builder.
*
* <p>
* Supports build of all Principal Registered Parameter Names of the JWE specification:
*
* <ul>
* <li>{@link #type(String) alg}</li>
* <li>{@link #contentType(String) typ}</li>
* <li>{@link #algorithm(String) cty}</li>
* <li>{@link #encryptionAlgorithm(String, int) enc}</li>
* <li>{@link #compressionAlgorithm(String) zip}</li>
* <li>{@link #keys(JWKSet) keys}</li>
* <li>{@link #JWKSet(String) jku}</li>
* <li>{@link #X509URL(String) x5u}</li>
* <li>{@link #X509CertificateChain(String...) x5c}</li>
* <li>{@link #X509CertificateSHA1Thumbprint(String) x5t}</li>
* <li>{@link #X509CertificateSHA256Thumbprint(String) x5t#S256}</li>
* </ul>
*
* <p>
* Example header:
*
* <pre>
* {
* "alg":"RSA1_5",
* "kid":"2011-04-29",
* "enc":"A128CBC-HS256",
* "jku":"https://server.example.com/keys.jwks"
* }
* </pre>
*
* @param <T> the generic type
* @param <B> the generic type
* @author Giriraj Sharma
*/
public class JWEBuilder<T extends JWE, B extends JWEBuilder<?, ?>> {
private final JsonObjectBuilder headerBuilder;
private final Class<T> tokenType;
/**
* Instantiates a new JWE builder.
*/
public JWEBuilder() {
this((Class<T>) JWE.class);
}
/**
* Instantiates a new {@link org.picketlink.json.jose.JWE} builder.
*
* @param tokenType the token type
*/
protected JWEBuilder(Class<T> tokenType) {
this.tokenType = tokenType;
this.headerBuilder = Json.createObjectBuilder();
}
/**
* Gets the token type.
*
* @return the token type
*/
protected Class<T> getTokenType() {
return this.tokenType;
}
/**
* Gets the header builder.
*
* @return the header builder
*/
protected JsonObjectBuilder getHeaderBuilder() {
return this.headerBuilder;
}
/**
* Sets the type of JSON Web Encryption
* <p>
* The typ (type) Header Parameter is used by JWS or JWE to declare the MIME Media Type [IANA.MediaTypes] of this complete
* JWS or JWE object. This is intended for use by the application when more than one kind of object could be present in an
* application data structure that can contain a JWS or JWE object; the application can use this value to disambiguate among
* the different kinds of objects that might be present. Use of this Header Parameter is OPTIONAL.
*
* @param type the String type
* @return
*/
public JWEBuilder<T, B> type(String type) {
header(HEADER_TYPE, type);
return this;
}
/**
* Sets the content type of JSON Web Encryption
*
* <p>
* The cty (content type) Header Parameter is used by JWS or JWE applications to declare the MIME Media Type
* [IANA.MediaTypes] of the secured content (the payload) or encrypted plaintext. This is intended for use by the
* application when more than one kind of object could be present in the JWS payload or JWE encrypted plaintext; the
* application can use this value to disambiguate among the different kinds of objects that might be present. Use of this
* Header Parameter is OPTIONAL.
*
* @param contentType the String content type
* @return
*/
public JWEBuilder<T, B> contentType(String contentType) {
header(HEADER_CONTENT_TYPE, contentType);
return this;
}
/**
* Sets the algorithm used to encrypt or determine the value of the Content Encryption Key (CEK).
*
* <p>
* The alg (algorithm) Header Parameter identifies the cryptographic algorithm used to secure the JWS or JWE. The signature,
* MAC, or plaintext value is not valid if the alg value does not represent a supported algorithm, or if there is not a key
* for use with that algorithm associated with the party that digitally signed or MACed the content. alg values 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. The alg value is a case-sensitive string containing a StringOrURI value.
*
* <ul>
* <li>RSA1_5
* <li>RSA-OAEP
* <li>RSA-OAEP-256
* </ul>
*
* @param algorithm the algorithm as a string
* @return
*/
public JWEBuilder<T, B> algorithm(String algorithm) {
header(ALG, algorithm);
return this;
}
/**
* Sets the encryption algorithm used to encrypt the Plaintext to produce the Ciphertext.
*
* <p>
* The enc (encryption algorithm) Header Parameter identifies the content encryption algorithm used to encrypt the Plaintext
* to produce the Ciphertext. This algorithm MUST be an AEAD algorithm with a specified key length. The recipient MUST
* reject the JWE if the enc value does not represent a supported algorithm. enc values 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. The enc value is a case-sensitive string containing a StringOrURI value.
*
* <ul>
* <li>ENC_A128CBC_HS256
* <li>ENC_A192CBC_HS384
* <li>ENC_A256CBC_HS512
* <li>ENC_A128GCM
* <li>ENC_A192GCM
* <li>ENC_A256GCM
* </ul>
*
* @param encAlgorithm the encryption algorithm
* @param cekBitLength the content encryption key bit length
* @return
*/
public JWEBuilder<T, B> encryptionAlgorithm(String encAlgorithm, int cekBitLength) {
header(ENC, encAlgorithm);
header(CEK_BITLENGTH, cekBitLength);
return this;
}
/**
* Sets the key identifier used to determine the private key needed to decrypt the JWE.
*
* <p>
* 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.
*
* @param keyId the key id
* @return
*/
public JWEBuilder<T, B> keyIdentifier(String keyId) {
header(KEY_ID, keyId);
return this;
}
/**
* Sets the compression algorithm. The zip (compression algorithm) applied to the Plaintext before encryption, if any. The
* zip value defined by this specification is:
*
* <ul>
* <li>DEF - Compression with the DEFLATE [RFC1951] algorithm</li>
* </ul>
*
* <p>
* Other values MAY be used. Compression algorithm values can be registered in the IANA JSON Web Encryption Compression
* Algorithm registry defined in [JWA]. The zip value is a case-sensitive string. If no zip parameter is present, no
* compression is applied to the Plaintext before encryption.
*
* @param zipAlgorithm the zip algorithm
* @return
*/
public JWEBuilder<T, B> compressionAlgorithm(String zipAlgorithm) {
header(COMPRESSION_ALG, zipAlgorithm);
return this;
}
/**
* Sets the JSON Web Key Set.
*
* <p>
* The JWK (JSON Web Key) Header Parameter is the public key that corresponds to the key used to digitally sign the JWS.
* This key is represented as a JSON Web Key [JWK]. Use of this Header Parameter is OPTIONAL.
*
* @param keySet the key set
* @return
*/
public JWEBuilder<T, B> keys(JWKSet keySet) {
header(HEADER_JSON_WEB_KEY, keySet.getJsonObject().getJsonArray(HEADER_JSON_WEB_KEY));
return this;
}
/**
* Returns the JWK Set consisting of JWK Keys.
*
* <p>
* The JWK Keys contains the public key to which the JWE was encrypted; this can be used to determine the private key needed
* to decrypt the JWE.
*
* @param keys the keys
* @return
*/
public JWEBuilder<T, B> keys(JWK... keys) {
JWKSet jwkSet = new JWKSet(keys);
return keys(jwkSet);
}
/**
* Updates the {@link org.picketlink.json.jose.JWE} JSON with the JWKSetURL.
*
* <p>
* The jku (JWK Set URL) Header Parameter is a URI [RFC3986] that refers to a resource for a set of JSON-encoded public
* keys, one of which corresponds to the key used to digitally sign the JWS or encrypt plaintext using JWE. The keys MUST be
* encoded as a JSON Web Key Set (JWK Set) [JWK]. The protocol used to acquire the resource MUST provide integrity
* protection; an HTTP GET request to retrieve the JWK Set MUST use TLS [RFC2818, RFC5246]; the identity of the server MUST
* be validated, as per Section 6 of RFC 6125 [RFC6125]. Use of this Header Parameter is OPTIONAL.
*
* @param jwkSetURL the JWK Set URL
* @return
*/
public JWEBuilder<T, B> JWKSet(String jwkSetURL) {
header(HEADER_JWK_SET_URL, jwkSetURL);
return this;
}
/**
* Sets the x509 URL.
*
* <p>
* 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 x509URL the x509 url
* @return
*/
public JWEBuilder<T, B> X509URL(String x509URL) {
header(X509_URL, x509URL);
return this;
}
/**
* Sets the x509 certificate chain.
*
* <p>
* 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 certificates the certificates
* @return
*/
public JWEBuilder<T, B> X509CertificateChain(String... certificates) {
if (certificates.length == 1) {
header(X509_CERTIFICATE_CHAIN, certificates[0]);
} else if (certificates.length > 1) {
JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
for (String operation : certificates) {
arrayBuilder.add(operation);
}
this.headerBuilder.add(X509_CERTIFICATE_CHAIN, arrayBuilder);
}
return this;
}
/**
* Sets the x509 SHA1 certificate thumbprint.
*
* <p>
* 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 JWEBuilder<T, B> X509CertificateSHA1Thumbprint(String sha1Thumbprint) {
header(X509_CERTIFICATE_SHA1_THUMBPRINT, sha1Thumbprint);
return this;
}
/**
* Sets the x509 SHA256 certificate thumbprint.
*
* <p>
* 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 JWEBuilder<T, B> X509CertificateSHA256Thumbprint(String sha256Thumbprint) {
header(X509_CERTIFICATE_SHA256_THUMBPRINT, sha256Thumbprint);
return this;
}
/**
* Updates {@link org.picketlink.json.jose.JWE} Header with the specified string header and its value(s).
*
* @param name the name
* @param value the value
* @return
*/
public JWEBuilder<T, B> header(String name, String... value) {
setString(this.headerBuilder, name, value);
return this;
}
/**
* Updates {@link org.picketlink.json.jose.JWE} Header with the specified string header and its value(s).
*
* @param name the name
* @param value the value
* @return
*/
public JWEBuilder<T, B> header(String name, int... value) {
setInt(this.headerBuilder, name, value);
return this;
}
/**
* Updates {@link org.picketlink.json.jose.JWE} Header with the specified string header and its value(s).
*
* @param name the name
* @param value the value
* @return
*/
public JWEBuilder<T, B> header(String name, List<JsonObject> value) {
setJsonObject(this.headerBuilder, name, value);
return this;
}
/**
* Updates {@link org.picketlink.json.jose.JWE} Header with the specified string header and its value(s).
*
* @param name the name
* @param value the value
* @return
*/
public JWEBuilder<T, B> header(String name, JsonArray value) {
setJsonObject(this.headerBuilder, name, value);
return this;
}
/**
* Updates the {@link javax.json.JsonObjectBuilder} with specified header parameter and its value(s).
*
* @param builderuilder
* @param name the name
* @param values the values
* @return
*/
private JWEBuilder<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;
}
/**
* Updates the {@link javax.json.JsonObjectBuilder} with specified header parameter and its value(s).
*
* @param builderuilder
* @param name the name
* @param values the values
* @return
*/
private JWEBuilder<T, B> setInt(JsonObjectBuilder builder, String name, int... values) {
if (values.length == 1) {
builder.add(name, values[0]);
} else if (values.length > 1) {
JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
for (int value : values) {
arrayBuilder.add(value);
}
builder.add(name, arrayBuilder);
}
return this;
}
/**
* POpulates the specified header parameter of {@link javax.json.JsonObjectBuilder} with its collection.
*
* @param builderuilder
* @param name the name
* @param values the values
* @return
*/
private JWEBuilder<T, B> setJsonObject(JsonObjectBuilder builder, String name, List<JsonObject> values) {
JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
Iterator<JsonObject> iterator = values.iterator();
while (iterator.hasNext()) {
arrayBuilder.add(iterator.next());
}
builder.add(name, arrayBuilder);
return this;
}
/**
* <p>
* Updates the the specified header of {@link javax.json.JsonObjectBuilder} with the {@link javax.json.JsonArray}.
* </p>
*
* @param builder the builder
* @param name the name of the header or claim
* @param values the values for the header or claim
* @return
*/
private JWEBuilder<T, B> setJsonObject(JsonObjectBuilder builder, String name, JsonArray values) {
builder.add(name, values);
return this;
}
/**
* Builds {@link javax.json.JsonObjectBuilder}.
*
* @return
*/
public T build() {
return build(this.headerBuilder.build());
}
/**
* Builds String JSON.
*
* @param json the json
* @return
*/
public T build(String json) {
byte[] keyParameters = b64Decode(json);
return build(Json.createReader(new ByteArrayInputStream(keyParameters)).readObject());
}
/**
* Builds {@link javax.json.JsonObject}.
*
* @param headersObject the headers object
* @return
*/
protected T build(JsonObject headersObject) {
try {
Constructor<T> constructor = this.tokenType.getDeclaredConstructor(JsonObject.class);
constructor.setAccessible(true);
return constructor.newInstance(headersObject);
} catch (Exception e) {
throw MESSAGES.couldNotCreateToken(this.tokenType, e);
}
}
}