/*
* The contents of this file are subject to the Common Public License Version 1.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.eclipse.org/legal/cpl-v10.html
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR APARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*  Copyright (C) 2017 Donovan Lampa <donovan.lampa@gmail.com>
*  Copyright (C) 2009-2017 The JRuby Team
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the EPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the EPL, the GPL or the LGPL.
*
*
* JRuby-OpenSSL includes software by The Legion of the Bouncy Castle Inc.
* Please, visit (http://bouncycastle.org/license.html) for licensing details.
*/
package org.jruby.ext.openssl;

import java.security.Security;
import java.util.HashMap;
import java.util.Map;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyFixnum;
import org.jruby.RubyModule;
import org.jruby.exceptions.RaiseException;
import org.jruby.runtime.builtin.IRubyObject;

/**
 * OCSP
 *
 * @author lampad
 */
public class OCSP {
    
    //Response has valid confirmations
    private static final String _RESPONSE_STATUS_SUCCESSFUL_STR = "RESPONSE_STATUS_SUCCESSFUL";
    private static final int _RESPONSE_STATUS_SUCCESSFUL = 0;
    //Illegal confirmation request
    private static final String _RESPONSE_STATUS_MALFORMEDREQUEST_STR = "RESPONSE_STATUS_MALFORMEDREQUEST";
    private static final int _RESPONSE_STATUS_MALFORMEDREQUEST = 1;
    //Internal error in issuer
    private static final String _RESPONSE_STATUS_INTERNALERROR_STR = "RESPONSE_STATUS_INTERNALERROR";
    private static final int _RESPONSE_STATUS_INTERNALERROR = 2;
    //Try again later
    private static final String _RESPONSE_STATUS_TRYLATER_STR = "RESPONSE_STATUS_TRYLATER";
    private static final int _RESPONSE_STATUS_TRYLATER = 3;
    //You must sign the request and resubmit
    private static final String _RESPONSE_STATUS_SIGREQUIRED_STR = "RESPONSE_STATUS_SIGREQUIRED";
    private static final int _RESPONSE_STATUS_SIGREQUIRED = 5;
    //Your request is unauthorized.
    private static final String _RESPONSE_STATUS_UNAUTHORIZED_STR = "RESPONSE_STATUS_UNAUTHORIZED";
    private static final int _RESPONSE_STATUS_UNAUTHORIZED = 6;
    
    private static final Map<Integer, String> responseMap;
    
    //The certificate was revoked for an unknown reason
    private static final int _REVOKED_STATUS_NOSTATUS = -1;
    //The certificate was revoked for an unspecified reason
    private static final int _REVOKED_STATUS_UNSPECIFIED = 0;
    //The certificate was revoked due to a key compromise
    private static final int _REVOKED_STATUS_KEYCOMPROMISE = 1;
    //This CA certificate was revoked due to a key compromise
    private static final int _REVOKED_STATUS_CACOMPROMISE = 2;
    //The certificate subject's name or other information changed
    private static final int _REVOKED_STATUS_AFFILIATIONCHANGED = 3;
    //The certificate was superseded by a new certificate
    private static final int _REVOKED_STATUS_SUPERSEDED = 4;
    //The certificate is no longer needed
    private static final int _REVOKED_STATUS_CESSATIONOFOPERATION = 5;
    //The certificate is on hold
    private static final int _REVOKED_STATUS_CERTIFICATEHOLD = 6;
    //The certificate was previously on hold and should now be removed from the CRL
    private static final int _REVOKED_STATUS_REMOVEFROMCRL = 8;
    
    //Do not include certificates in the response 
    private static final int _NOCERTS = 0x1;
    //Do not search certificates contained in the response for a signer 
    private static final int _NOINTERN = 0x2;
    //Do not check the signature on the response 
    private static final int _NOSIGS = 0x4;
    //Do not verify the certificate chain on the response
    private static final int _NOCHAIN = 0x8;
    //Do not verify the response at all
    private static final int _NOVERIFY = 0x10;
    //Do not check trust
    private static final int _NOEXPLICIT = 0x20;
    //(This flag is not used by OpenSSL 1.0.1g) 
    private static final int _NOCASIGN = 0x40;
    //(This flag is not used by OpenSSL 1.0.1g)
    private static final int _NODELEGATED = 0x80;
    //Do not make additional signing certificate checks
    private static final int _NOCHECKS = 0x100;
    //Do not verify additional certificates
    private static final int _TRUSTOTHER = 0x200;
    //Identify the response by signing the certificate key ID
    private static final int _RESPID_KEY = 0x400;
    //Do not include producedAt time in response 
    private static final int _NOTIME = 0x800;
    
    /*
     * Indicates the certificate is not revoked but does not necessarily mean
     * the certificate was issued or that this response is within the
     * certificate's validity interval 
     */
    private static final int _V_CERTSTATUS_GOOD = 0;
    /* Indicates the certificate has been revoked either permanently or
     * temporarily (on hold). 
     */
    private static final int _V_CERTSTATUS_REVOKED = 1;
    /* Indicates the responder does not know about the certificate being
     * requested. 
     */
    private static final int _V_CERTSTATUS_UNKNOWN = 2;
    
    //The responder ID is based on the key name.
    private static final int _V_RESPID_NAME = 0;
    //The responder ID is based on the public key.
    private static final int _V_RESPID_KEY =1;
    
    static {
        Map<Integer, String> resMap = new HashMap<Integer, String>();
        resMap.put(_RESPONSE_STATUS_SUCCESSFUL, _RESPONSE_STATUS_SUCCESSFUL_STR);
        resMap.put(_RESPONSE_STATUS_MALFORMEDREQUEST, _RESPONSE_STATUS_MALFORMEDREQUEST_STR);
        resMap.put(_RESPONSE_STATUS_INTERNALERROR, _RESPONSE_STATUS_INTERNALERROR_STR);
        resMap.put(_RESPONSE_STATUS_TRYLATER, _RESPONSE_STATUS_TRYLATER_STR);
        resMap.put(_RESPONSE_STATUS_SIGREQUIRED, _RESPONSE_STATUS_SIGREQUIRED_STR);
        resMap.put(_RESPONSE_STATUS_UNAUTHORIZED, _RESPONSE_STATUS_UNAUTHORIZED_STR);
        responseMap = resMap;
    }
    
    public static void createOCSP(final Ruby runtime, final RubyModule OpenSSL) {
        final RubyModule OCSP = OpenSSL.defineModuleUnder("OCSP");
        final RubyClass OpenSSLError = OpenSSL.getClass("OpenSSLError");
        Security.addProvider(new BouncyCastleProvider());
        OCSP.defineClassUnder("OCSPError", OpenSSLError, OpenSSLError.getAllocator());
        
        OCSPBasicResponse.createBasicResponse(runtime, OCSP);
        OCSPCertificateId.createCertificateId(runtime, OCSP);
        OCSPRequest.createRequest(runtime, OCSP);
        OCSPResponse.createResponse(runtime, OCSP);
        OCSPSingleResponse.createSingleResponse(runtime, OCSP);
        
        //ResponseStatuses
        OCSP.setConstant(_RESPONSE_STATUS_SUCCESSFUL_STR, runtime.newFixnum(_RESPONSE_STATUS_SUCCESSFUL));
        OCSP.setConstant(_RESPONSE_STATUS_MALFORMEDREQUEST_STR, runtime.newFixnum(_RESPONSE_STATUS_MALFORMEDREQUEST));
        OCSP.setConstant(_RESPONSE_STATUS_INTERNALERROR_STR, runtime.newFixnum(_RESPONSE_STATUS_INTERNALERROR));
        OCSP.setConstant(_RESPONSE_STATUS_TRYLATER_STR, runtime.newFixnum(_RESPONSE_STATUS_TRYLATER));
        OCSP.setConstant(_RESPONSE_STATUS_SIGREQUIRED_STR, runtime.newFixnum(_RESPONSE_STATUS_SIGREQUIRED));
        OCSP.setConstant(_RESPONSE_STATUS_UNAUTHORIZED_STR, runtime.newFixnum(_RESPONSE_STATUS_UNAUTHORIZED));

        //RevocationReasons
        OCSP.setConstant("REVOKED_STATUS_NOSTATUS", runtime.newFixnum(_REVOKED_STATUS_NOSTATUS));
        OCSP.setConstant("REVOKED_STATUS_UNSPECIFIED", runtime.newFixnum(_REVOKED_STATUS_UNSPECIFIED));
        OCSP.setConstant("REVOKED_STATUS_KEYCOMPROMISE", runtime.newFixnum(_REVOKED_STATUS_KEYCOMPROMISE));
        OCSP.setConstant("REVOKED_STATUS_CACOMPROMISE", runtime.newFixnum(_REVOKED_STATUS_CACOMPROMISE));
        OCSP.setConstant("REVOKED_STATUS_AFFILIATIONCHANGED", runtime.newFixnum(_REVOKED_STATUS_AFFILIATIONCHANGED));
        OCSP.setConstant("REVOKED_STATUS_SUPERSEDED", runtime.newFixnum(_REVOKED_STATUS_SUPERSEDED));
        OCSP.setConstant("REVOKED_STATUS_CESSATIONOFOPERATION", runtime.newFixnum(_REVOKED_STATUS_CESSATIONOFOPERATION));
        OCSP.setConstant("REVOKED_STATUS_CERTIFICATEHOLD", runtime.newFixnum(_REVOKED_STATUS_CERTIFICATEHOLD));
        OCSP.setConstant("REVOKED_STATUS_REMOVEFROMCRL", runtime.newFixnum(_REVOKED_STATUS_REMOVEFROMCRL));

        OCSP.setConstant("NOCERTS", runtime.newFixnum(_NOCERTS));
        OCSP.setConstant("NOINTERN", runtime.newFixnum(_NOINTERN));
        OCSP.setConstant("NOSIGS", runtime.newFixnum(_NOSIGS));
        OCSP.setConstant("NOCHAIN", runtime.newFixnum(_NOCHAIN));
        OCSP.setConstant("NOVERIFY", runtime.newFixnum(_NOVERIFY));
        OCSP.setConstant("NOEXPLICIT", runtime.newFixnum(_NOEXPLICIT));
        OCSP.setConstant("NOCASIGN", runtime.newFixnum(_NOCASIGN));
        OCSP.setConstant("NODELEGATED", runtime.newFixnum(_NODELEGATED));
        OCSP.setConstant("NOCHECKS", runtime.newFixnum(_NOCHECKS));
        OCSP.setConstant("TRUSTOTHER", runtime.newFixnum(_TRUSTOTHER));
        OCSP.setConstant("RESPID_KEY", runtime.newFixnum(_RESPID_KEY));
        OCSP.setConstant("NOTIME", runtime.newFixnum(_NOTIME));
        
        OCSP.setConstant("V_CERTSTATUS_GOOD", runtime.newFixnum(_V_CERTSTATUS_GOOD));
        OCSP.setConstant("V_CERTSTATUS_REVOKED", runtime.newFixnum(_V_CERTSTATUS_REVOKED));
        OCSP.setConstant("V_CERTSTATUS_UNKNOWN", runtime.newFixnum(_V_CERTSTATUS_UNKNOWN));

        OCSP.setConstant("V_RESPID_NAME", runtime.newFixnum(_V_RESPID_NAME));
        OCSP.setConstant("V_RESPID_KEY", runtime.newFixnum(_V_RESPID_KEY));
    }
    
    public static String getResponseStringForValue(IRubyObject fixnum) {
        RubyFixnum rubyFixnum = (RubyFixnum) fixnum;
        return responseMap.get((int)rubyFixnum.getLongValue());
    }
    
    static RaiseException newOCSPError(Ruby runtime, Exception ex) {
        return Utils.newError(runtime, _OCSP(runtime).getClass("OCSPError"), ex);
    }
    
    static RubyModule _OCSP(final Ruby runtime) {
        return (RubyModule) runtime.getModule("OpenSSL").getConstant("OCSP");
    }

}
