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

org.jose4j.jws.JsonWebSignature Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2012-2017 Brian Campbell
 *
 * 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.jose4j.jws;

import org.jose4j.jwa.AlgorithmConstraints;
import org.jose4j.jwa.AlgorithmFactory;
import org.jose4j.jwa.AlgorithmFactoryFactory;
import org.jose4j.jwa.CryptoPrimitive;
import org.jose4j.jwx.CompactSerializer;
import org.jose4j.jwx.HeaderParameterNames;
import org.jose4j.jwx.JsonWebStructure;
import org.jose4j.keys.KeyPersuasion;
import org.jose4j.lang.IntegrityException;
import org.jose4j.lang.InvalidAlgorithmException;
import org.jose4j.lang.JoseException;
import org.jose4j.lang.StringUtil;

import javax.crypto.Mac;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.Key;
import java.security.Signature;

/**
 * The JsonWebSignature class is used to produce and consume JSON Web Signature (JWS) as defined in
 * RFC 7515.
 */
public class JsonWebSignature extends JsonWebStructure
{
    public static final short COMPACT_SERIALIZATION_PARTS = 3;

    private byte[] payloadBytes;
    private String payloadCharEncoding = StringUtil.UTF_8;
    private String encodedPayload;

    private Boolean validSignature;
    private CryptoPrimitive signingPrimitive;

    public JsonWebSignature()
    {
        if (!Boolean.getBoolean("org.jose4j.jws.default-allow-none"))
        {
            setAlgorithmConstraints(AlgorithmConstraints.DISALLOW_NONE);
        }
    }

    /**
     * Sets the JWS payload as a string.
     * Use {@link #setPayloadCharEncoding(String)} before calling this method, to use a character
     * encoding other than UTF-8.
     * @param payload the payload, as a string, to be singed.
     */
    public void setPayload(String payload)
    {
        this.payloadBytes = StringUtil.getBytesUnchecked(payload, payloadCharEncoding);
        this.encodedPayload = null;
    }

    /**
     * Get the JWS payload.
     * @return the sequence of bytes that make up the JWS payload.
     * @throws JoseException if the JWS signature is invalid or an error condition is encountered during the signature verification process
     */
    public byte[] getPayloadBytes() throws JoseException
    {
        if (!verifySignature())
        {
            throw new IntegrityException("JWS signature is invalid.");
        }

        return payloadBytes;
    }

    /**
     * Get the JWS payload. Unlike {@link #getPayloadBytes()} the signature is not
     * verified when calling this method.
     * @return the sequence of bytes that make up the JWS payload.
     */
    public byte[] getUnverifiedPayloadBytes()
    {
        return payloadBytes;
    }


    /**
     * Sets the JWS payload.
     * @param payloadBytes the payload, as a byte array, to be singed
     */
    public void setPayloadBytes(byte[] payloadBytes)
    {
        this.payloadBytes = payloadBytes;
    }

    protected void setCompactSerializationParts(String[] parts) throws JoseException
    {
        if (parts.length != COMPACT_SERIALIZATION_PARTS)
        {
            throw new JoseException("A JWS Compact Serialization must have exactly "+COMPACT_SERIALIZATION_PARTS+" parts separated by period ('.') characters");
        }

        setEncodedHeader(parts[0]);
        if (isRfc7797UnencodedPayload())
        {
            setPayload(parts[1]);
        }
        else
        {
            setEncodedPayload(parts[1]);
        }

        setSignature(base64url.base64UrlDecode(parts[2]));
    }

    /**
     * 

* Sign and produce the JWS Compact Serialization. *

*

* The JWS Compact Serialization represents digitally signed or MACed * content as a compact, URL-safe string. This string is: *

* BASE64URL(UTF8(JWS Protected Header)) || '.' || * BASE64URL(JWS Payload) || '.' || * BASE64URL(JWS Signature) *

* @return the Compact Serialization: the encoded header + "." + the encoded payload + "." + the encoded signature * @throws JoseException if an error condition is encountered during the process */ public String getCompactSerialization() throws JoseException { this.sign(); String payload; if (isRfc7797UnencodedPayload()) { payload = getStringPayload(); if (payload.contains(".")) { throw new JoseException("per https://tools.ietf.org/html/rfc7797#section-5.2 " + "when using the JWS Compact Serialization, unencoded non-detached " + "payloads using period ('.') characters would cause parsing errors; " + "such payloads MUST NOT be used with the JWS Compact Serialization."); } } else { payload = getEncodedPayload(); } return CompactSerializer.serialize(getEncodedHeader(), payload, getEncodedSignature()); } /** * Produces the compact serialization with an empty/detached payload as described in * Appendix F, Detached Content, of the JWS spec * though providing library support rather than making the application do it all as * described therein. * * @return the encoded header + ".." + the encoded signature * @throws JoseException if an error condition is encountered during the signing process */ public String getDetachedContentCompactSerialization() throws JoseException { this.sign(); return CompactSerializer.serialize(getEncodedHeader(), "", getEncodedSignature()); } /** * Create, initialize (using the key and {@link org.jose4j.jca.ProviderContext}) and return the {@link CryptoPrimitive} that * this JWS instance will use for signing. * This can optionally be called after setting the key (and maybe ProviderContext) but before getting the compact * serialization (which is when the singing magic happens). * This method provides access to the underlying primitive instance (e.g. a {@link Signature}), which allows execution of * the operation to be gated by some approval or authorization. * For example, signing on Android with a key that was set to require user authentication when created needs a biometric * prompt to allow the signature to execute with the key. * * @return a CryptoPrimitive containing either a {@link Signature} or {@link Mac}, or null * @throws JoseException if an error condition is encountered during the initialization process */ public CryptoPrimitive prepareSigningPrimitive() throws JoseException { signingPrimitive = createSigningPrimitive(); return signingPrimitive; } private CryptoPrimitive createSigningPrimitive() throws JoseException { JsonWebSignatureAlgorithm algorithm = getAlgorithm(); Key signingKey = getKey(); if (isDoKeyValidation()) { algorithm.validateSigningKey(signingKey); } return algorithm.prepareForSign(signingKey, getProviderCtx()); } /** * Compute the JWS signature. * @throws JoseException if an error condition is encountered during the signing process */ public void sign() throws JoseException { CryptoPrimitive cryptoPrimitive = (signingPrimitive == null) ? createSigningPrimitive() : signingPrimitive; byte[] inputBytes = getSigningInputBytes(); byte[] signatureBytes = getAlgorithm().sign(cryptoPrimitive, inputBytes); setSignature(signatureBytes); } @Override protected void onNewKey() { validSignature = null; } /** * Verify the signature of the JWS. * @return true if the signature is valid, false otherwise * @throws JoseException if an error condition is encountered during the signature verification process */ public boolean verifySignature() throws JoseException { JsonWebSignatureAlgorithm algorithm = getAlgorithm(); Key verificationKey = getKey(); if (isDoKeyValidation()) { algorithm.validateVerificationKey(verificationKey); } if (validSignature == null) { checkCrit(); byte[] signatureBytes = getSignature(); byte[] inputBytes = getSigningInputBytes(); validSignature = algorithm.verifySignature(signatureBytes, verificationKey, inputBytes, getProviderCtx()); } return validSignature; } @Override protected boolean isSupportedCriticalHeader(String headerName) { return HeaderParameterNames.BASE64URL_ENCODE_PAYLOAD.equals(headerName); } @Override public JsonWebSignatureAlgorithm getAlgorithm() throws InvalidAlgorithmException { return getAlgorithm(true); } @Override public JsonWebSignatureAlgorithm getAlgorithmNoConstraintCheck() throws InvalidAlgorithmException { return getAlgorithm(false); } private JsonWebSignatureAlgorithm getAlgorithm(boolean checkConstraints) throws InvalidAlgorithmException { String algo = getAlgorithmHeaderValue(); if (algo == null) { throw new InvalidAlgorithmException("Signature algorithm header ("+HeaderParameterNames.ALGORITHM+") not set."); } if (checkConstraints) { getAlgorithmConstraints().checkConstraint(algo); } AlgorithmFactoryFactory factoryFactory = AlgorithmFactoryFactory.getInstance(); AlgorithmFactory jwsAlgorithmFactory = factoryFactory.getJwsAlgorithmFactory(); return jwsAlgorithmFactory.getAlgorithm(algo); } private byte[] getSigningInputBytes() throws JoseException { /* https://tools.ietf.org/html/rfc7797#section-3 +-------+-----------------------------------------------------------+ | "b64" | JWS Signing Input Formula | +-------+-----------------------------------------------------------+ | true | ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.' || | | | BASE64URL(JWS Payload)) | | | | | false | ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.') || | | | JWS Payload | +-------+-----------------------------------------------------------+ */ if (!isRfc7797UnencodedPayload()) { String signingInputString = CompactSerializer.serialize(getEncodedHeader(), getEncodedPayload()); return StringUtil.getBytesAscii(signingInputString); } else { try { ByteArrayOutputStream os = new ByteArrayOutputStream(); os.write(StringUtil.getBytesAscii(getEncodedHeader())); os.write(0x2e); // ascii for "." os.write(payloadBytes); return os.toByteArray(); } catch (IOException e) { throw new JoseException("This should never happen from a ByteArrayOutputStream", e); } } } protected boolean isRfc7797UnencodedPayload() { Object b64 = headers.getObjectHeaderValue(HeaderParameterNames.BASE64URL_ENCODE_PAYLOAD); return (b64 != null && b64 instanceof Boolean && !(Boolean)b64); } /** * Gets the JWS payload as a string. * Use {@link #setPayloadCharEncoding(String)} before calling this method, to use a character * encoding other than UTF-8. * @return the JWS payload * @throws JoseException if the JWS signature is invalid or an error condition is encountered during the signature verification process */ public String getPayload() throws JoseException { if (!Boolean.getBoolean("org.jose4j.jws.getPayload-skip-verify") && !verifySignature()) { throw new IntegrityException("JWS signature is invalid."); } return getStringPayload(); } /** * Gets the JWS payload as a string. Unlike {@link #getPayload()} the signature is not * verified when calling this method. * Use {@link #setPayloadCharEncoding(String)} before calling this method, to use a character * encoding other than UTF-8. * @return the JWS payload */ public String getUnverifiedPayload() { return getStringPayload(); } private String getStringPayload() { return StringUtil.newString(payloadBytes, payloadCharEncoding); } /** * Gets the character encoding used for the string representation of the JWS payload. * The default encoding is UTF-8. * @return the character encoding */ public String getPayloadCharEncoding() { return payloadCharEncoding; } /** * Sets the character encoding used for the string representation of the JWS payload (i.e. * when using {@link #getPayload()}, {@link #getUnverifiedPayload()}, or {@link #setPayload(String)}). * The default encoding is UTF-8. * @param payloadCharEncoding the character encoding to use for the string representation of the JWS payload */ public void setPayloadCharEncoding(String payloadCharEncoding) { this.payloadCharEncoding = payloadCharEncoding; } public String getKeyType() throws InvalidAlgorithmException { return getAlgorithmNoConstraintCheck().getKeyType(); } public KeyPersuasion getKeyPersuasion() throws InvalidAlgorithmException { return getAlgorithmNoConstraintCheck().getKeyPersuasion(); } public void setEncodedPayload(String encodedPayload) { this.encodedPayload = encodedPayload; this.payloadBytes = base64url.base64UrlDecode(encodedPayload); } /** * Gets the base64url encoded JWS Payload. * @return the base64url encoded JWS Payload. */ public String getEncodedPayload() { return (encodedPayload != null) ? encodedPayload : base64url.base64UrlEncode(payloadBytes); } public String getEncodedSignature() { return base64url.base64UrlEncode(getSignature()); } protected byte[] getSignature() { return getIntegrity(); } protected void setSignature(byte[] signature) { setIntegrity(signature); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy