/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.apache.cxf.rs.security.jose.jws;

import java.util.ArrayList;
import java.util.List;

import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;

import org.junit.Assert;
import org.junit.Test;

public class JwsCompactHeaderTest extends Assert {

    /**
     * JWS string, which lacks the "alg" header field.
     * 
     * => Must be rejected by verification operation, since the spec declares
     * that the "alg" header field must be present in the compact serialization.
     */
    public static final String MISSING_ALG_HEADER_FIELD_IN_JWS = 
        "eyAiZ2xhIiA6ICJDQU1IIiB9.eyAibXNnIjogIllvdSBjYW4ndCB0b3VjaCB0aGlzISIgfQ"
        + ".Sqd_AuwlPPqv4L1EV4zPuR-HfFJpe9kOfvc597RlcoE";

    /**
     * JWS string, which contains two "alg" header fields. Bogus "alg" header
     * field first.
     * 
     * => Must be rejected by verification operation, since the spec declares
     * that the "alg" header field must be present once in the compact
     * serialization.
     */
    public static final String TWO_ALG_HEADER_FIELDS_IN_JWS_BOGUS_FIRST = 
        "eyAiYWxnIjogIkJvZ3VzIiwgImFsZyI6ICJIUzI1NiIgfQ.eyAibXNnIjogIllvdSBjYW4ndCB0b3VjaCB0aGlzISIgfQ"
        + ".FIgpDi1Wp9iIxxXfBw8Zce2kiZ8gmqAaVYPduRFR8kU";

    /**
     * JWS string, which contains two "alg" header fields. Bogus "alg" header
     * field last.
     * 
     * => Must be rejected by verification operation, since the spec declares
     * that the "alg" header field must be present once in the compact
     * serialization.
     */
    public static final String TWO_ALG_HEADER_FIELDS_IN_JWS_BOGUS_LAST = 
        "eyAiYWxnIjogIkhTMjU2IiwgImFsZyI6ICJCb2d1cyIgfQ.eyAibXNnIjogIllvdSBjYW4ndCB0b3VjaCB0aGlzISIgfQ"
        + ".Ftwla-nAg0Nty8ILVhjlIETOy2Tw1JsD3bBq55AS0PU";

    /**
     * JWS string, which contains an invalid "alg" header field value.
     * 
     * (1): Algorithm not supported/known
     * 
     * => Must be rejected by verification operation, since the spec declares
     * that the signature is not valid if the "alg" value does not represent a
     * supported algorithm. "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.
     */
    public static final String INVALID_ALG_HEADER_VALUE_IN_JWS_1 = "tba";

    /**
     * JWS string, which contains an invalid "alg" header field value.
     * 
     * (2): Wrong value encoding
     * 
     * => Must be rejected by verification operation, since the spec declares
     * that the "alg" value is a case-sensitive string containing a StringOrURI
     * value.
     */
    public static final String INVALID_ALG_HEADER_VALUE_IN_JWS_2 = "tba";

    /**
     * JWS string, which contains a "alg" header field value of "none". The
     * signature has been generated with "HS256" and the signed JWS has been
     * altered afterwards to the value "none".
     * 
     * => Must be rejected by verification operation, since the "none" algorithm
     * is considered harmful.
     */
    public static final String ALG_HEADER_VALUE_HS256_IN_JWS = 
        "eyAiYWxnIjogIkhTMjU2IiB9"
        + ".eyAibXNnIjogIllvdSBjYW4ndCB0b3VjaCB0aGlzISIgfQ"
        + ".as_gclokwAmukh3zVF1X5sUCCfSc8TbjDdhdvk6C5c8";
    public static final String ALG_HEADER_VALUE_NONE_IN_JWS = 
        "eyAiYWxnIjogIm5vbmUiIH0"
        + ".eyAibXNnIjogIllvdSBjYW4ndCB0b3VjaCB0aGlzISIgfQ"
        + ".as_gclokwAmukh3zVF1X5sUCCfSc8TbjDdhdvk6C5c8";

    
    /**
     * Support material (keys, etc.)
     */
    private static final String ENCODED_MAC_KEY = "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75"
                    + "aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow";
    
    // JWS string, which contains crit header field
    // JWS string, which contains more than three parts
    // JWS string, which contains less than three parts
    // JWS string, which contains null bytes padding

    @Test
    public void verifyJwsWithMissingAlgHeaderField() throws Exception {
        JwsCompactConsumer jwsConsumer = new JwsCompactConsumer(MISSING_ALG_HEADER_FIELD_IN_JWS);

        assertFalse(jwsConsumer.verifySignatureWith(new HmacJwsSignatureVerifier(ENCODED_MAC_KEY, 
                                                        SignatureAlgorithm.HS256)));
    }

    @Test
    public void verifyJwsWithTwoAlgHeaderFieldsBogusFieldFirst() throws Exception {
        JwsCompactConsumer jwsConsumer = new JwsCompactConsumer(TWO_ALG_HEADER_FIELDS_IN_JWS_BOGUS_FIRST);

        boolean result = jwsConsumer.verifySignatureWith(new HmacJwsSignatureVerifier(ENCODED_MAC_KEY,
                                                                     SignatureAlgorithm.HS256));
        assertFalse(result);
    }

    @Test
    public void verifyJwsWithTwoAlgHeaderFieldsBogusFieldLast() throws Exception {
        JwsCompactConsumer jwsConsumer = new JwsCompactConsumer(TWO_ALG_HEADER_FIELDS_IN_JWS_BOGUS_LAST);

        assertFalse(jwsConsumer.verifySignatureWith(new HmacJwsSignatureVerifier(ENCODED_MAC_KEY,
                                                        SignatureAlgorithm.HS256)));
    }
    
    @Test
    public void verifyJwsWithAlgHeaderValueNone() throws Exception {
        JwsCompactConsumer jwsConsumerOriginal = new JwsCompactConsumer(ALG_HEADER_VALUE_HS256_IN_JWS);
        
        JwsCompactConsumer jwsConsumerAltered = new JwsCompactConsumer(ALG_HEADER_VALUE_NONE_IN_JWS);

        assertTrue(jwsConsumerOriginal.verifySignatureWith(new HmacJwsSignatureVerifier(ENCODED_MAC_KEY,
                                                           SignatureAlgorithm.HS256)));
        
        assertFalse(jwsConsumerAltered.verifySignatureWith(new HmacJwsSignatureVerifier(ENCODED_MAC_KEY,
                                                           SignatureAlgorithm.HS256)));
    }

    @Test
    public void testCriticalHeader() {
        String payload = "this is a JWS with critical header";
        String criticalParameter = "criticalParameter";
        String criticalParameter1 = "criticalParameter1";
        String criticalParameter2 = "criticalParameter2";
        String criticalParameter3 = "criticalParameter3";
        String criticalValue = "criticalValue";
        String criticalValue1 = "criticalValue1";
        String criticalValue2 = "criticalValue2";
        String criticalValue3 = "criticalValue3";
        JwsCompactProducer producer = new JwsCompactProducer(payload);
        producer.getJwsHeaders().setSignatureAlgorithm(SignatureAlgorithm.HS512);
        List<String> criticalHeader = new ArrayList<String>();
        criticalHeader.add(criticalParameter1);
        producer.getJwsHeaders().setCritical(criticalHeader);
        producer.signWith(new HmacJwsSignatureProvider(ENCODED_MAC_KEY, SignatureAlgorithm.HS256));
        String signedJws = producer.getSignedEncodedJws();
        JwsCompactConsumer consumer = new JwsCompactConsumer(signedJws);
        assertFalse(consumer.validateCriticalHeaders());
        
        criticalHeader.add(criticalParameter2);
        criticalHeader.add(criticalParameter3);
        producer = new JwsCompactProducer(payload);
        producer.getJwsHeaders().setSignatureAlgorithm(SignatureAlgorithm.HS512);
        producer.getJwsHeaders().setCritical(criticalHeader);
        producer.getJwsHeaders().setHeader(criticalParameter1, criticalValue1);
        producer.getJwsHeaders().setHeader(criticalParameter2, criticalValue2);
        producer.getJwsHeaders().setHeader(criticalParameter3, criticalValue3);
        producer.signWith(new HmacJwsSignatureProvider(ENCODED_MAC_KEY, SignatureAlgorithm.HS256));
        signedJws = producer.getSignedEncodedJws();
        consumer = new JwsCompactConsumer(signedJws);
        assertTrue(consumer.validateCriticalHeaders());
    
        criticalHeader = new ArrayList<String>();
        criticalHeader.add(criticalParameter);
        criticalHeader.add(criticalParameter);
        producer = new JwsCompactProducer(payload);
        producer.getJwsHeaders().setSignatureAlgorithm(SignatureAlgorithm.HS512);
        producer.getJwsHeaders().setHeader(criticalParameter, criticalValue);
        producer.getJwsHeaders().setCritical(criticalHeader);
        producer.signWith(new HmacJwsSignatureProvider(ENCODED_MAC_KEY, SignatureAlgorithm.HS256));
        signedJws = producer.getSignedEncodedJws();
        consumer = new JwsCompactConsumer(signedJws);
        assertFalse(consumer.validateCriticalHeaders());
    }
}

