/**
 * 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.jwe;

import java.security.KeyPair;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;

import org.apache.cxf.common.util.Base64UrlUtility;
import org.apache.cxf.common.util.StringUtils;
import org.apache.cxf.rs.security.jose.jwa.ContentAlgorithm;
import org.apache.cxf.rs.security.jose.jwa.KeyAlgorithm;
import org.apache.cxf.rs.security.jose.jwk.JwkUtils;
import org.apache.cxf.rt.security.crypto.CryptoUtils;


public class EcdhDirectKeyJweEncryption extends JweEncryption {
    public EcdhDirectKeyJweEncryption(ECPublicKey peerPublicKey,
                                      String curve,
                                      ContentAlgorithm ctAlgo) {
        this(peerPublicKey, curve, null, null, ctAlgo);
    }
    public EcdhDirectKeyJweEncryption(ECPublicKey peerPublicKey,
                                      String curve,
                                      String apuString,
                                      String apvString,
                                      ContentAlgorithm ctAlgo) {
        super(new EcdhDirectKeyEncryptionAlgorithm(),
              new EcdhAesGcmContentEncryptionAlgorithm(peerPublicKey,
                                                       curve,
                                                       apuString,
                                                       apvString,
                                                       ctAlgo));
    }
    protected static class EcdhDirectKeyEncryptionAlgorithm extends DirectKeyEncryptionAlgorithm {
        protected void checkKeyEncryptionAlgorithm(JweHeaders headers) {
            headers.setKeyEncryptionAlgorithm(KeyAlgorithm.ECDH_ES_DIRECT);
        }
    }
    protected static class EcdhAesGcmContentEncryptionAlgorithm extends AesGcmContentEncryptionAlgorithm {
        private EcdhHelper helper;
        public EcdhAesGcmContentEncryptionAlgorithm(ECPublicKey peerPublicKey,
                                                    String curve,
                                                    String apuString,
                                                    String apvString,
                                                    ContentAlgorithm ctAlgo) {
            super(ctAlgo);
            helper = new EcdhHelper(peerPublicKey, curve, apuString, apvString, ctAlgo.getJwaName());
        }
        public byte[] getContentEncryptionKey(JweHeaders headers) {
            return helper.getDerivedKey(headers);
        }
    }

    protected static class EcdhHelper {
        private ECPublicKey peerPublicKey;
        private String ecurve;
        private byte[] apuBytes;
        private byte[] apvBytes;
        private String ctAlgo;
        public EcdhHelper(ECPublicKey peerPublicKey,
                                                    String curve,
                                                    String apuString,
                                                    String apvString,
                                                    String ctAlgo) {
            this.ctAlgo = ctAlgo;
            this.peerPublicKey = peerPublicKey;
            this.ecurve = curve;
            // JWA spec suggests the "apu" field MAY either be omitted or
            // represent a random 512-bit value (...) and the "apv" field SHOULD NOT be present."
            this.apuBytes = toApuBytes(apuString);
            this.apvBytes = toBytes(apvString);
        }
        public byte[] getDerivedKey(JweHeaders headers) {
            KeyPair pair = CryptoUtils.generateECKeyPair(ecurve);
            ECPublicKey publicKey = (ECPublicKey)pair.getPublic();
            ECPrivateKey privateKey = (ECPrivateKey)pair.getPrivate();
            ContentAlgorithm jwtAlgo = ContentAlgorithm.valueOf(ctAlgo);

            headers.setHeader("apu", Base64UrlUtility.encode(apuBytes));
            headers.setHeader("apv", Base64UrlUtility.encode(apvBytes));
            headers.setJsonWebKey("epk", JwkUtils.fromECPublicKey(publicKey, ecurve));

            return JweUtils.getECDHKey(privateKey, peerPublicKey, apuBytes, apvBytes,
                                       jwtAlgo.getJwaName(), jwtAlgo.getKeySizeBits());

        }
        private byte[] toApuBytes(String apuString) {
            if (apuString != null) {
                return toBytes(apuString);
            }
            return CryptoUtils.generateSecureRandomBytes(512 / 8);

        }
        private byte[] toBytes(String str) {
            return str == null ? null : StringUtils.toBytesUTF8(str);
        }

    }

}
