// 
// Decompiled by Procyon v0.6.0
// 

package com.google.crypto.tink.subtle;

import com.google.crypto.tink.internal.Util;
import java.util.Arrays;
import java.security.MessageDigest;
import java.math.BigInteger;
import com.google.crypto.tink.AccessesPartialKey;
import java.security.GeneralSecurityException;
import java.security.NoSuchProviderException;
import java.security.spec.KeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.interfaces.RSAPublicKey;
import java.security.KeyFactory;
import com.google.crypto.tink.signature.internal.RsaSsaPssVerifyConscrypt;
import com.google.crypto.tink.signature.RsaSsaPssPublicKey;
import com.google.crypto.tink.signature.RsaSsaPssParameters;
import com.google.crypto.tink.internal.EnumTypeProtoConverter;
import com.google.crypto.tink.config.internal.TinkFipsUtil;
import com.google.errorprone.annotations.Immutable;
import com.google.crypto.tink.PublicKeyVerify;

@Immutable
public final class RsaSsaPssVerifyJce implements PublicKeyVerify
{
    public static final TinkFipsUtil.AlgorithmFipsCompatibility FIPS;
    static final EnumTypeProtoConverter<Enums.HashType, RsaSsaPssParameters.HashType> HASH_TYPE_CONVERTER;
    private static final byte[] EMPTY;
    private static final byte[] legacyMessageSuffix;
    private final PublicKeyVerify verify;
    
    @AccessesPartialKey
    public static PublicKeyVerify create(final RsaSsaPssPublicKey key) throws GeneralSecurityException {
        try {
            return RsaSsaPssVerifyConscrypt.create(key);
        }
        catch (final NoSuchProviderException ex) {
            final KeyFactory keyFactory = EngineFactory.KEY_FACTORY.getInstance("RSA");
            final RSAPublicKey publicKey = (RSAPublicKey)keyFactory.generatePublic(new RSAPublicKeySpec(key.getModulus(), key.getParameters().getPublicExponent()));
            final RsaSsaPssParameters params = key.getParameters();
            return new InternalImpl(publicKey, (Enums.HashType)RsaSsaPssVerifyJce.HASH_TYPE_CONVERTER.toProtoEnum(params.getSigHashType()), (Enums.HashType)RsaSsaPssVerifyJce.HASH_TYPE_CONVERTER.toProtoEnum(params.getMgf1HashType()), params.getSaltLengthBytes(), key.getOutputPrefix().toByteArray(), key.getParameters().getVariant().equals(RsaSsaPssParameters.Variant.LEGACY) ? RsaSsaPssVerifyJce.legacyMessageSuffix : RsaSsaPssVerifyJce.EMPTY);
        }
    }
    
    private static RsaSsaPssParameters.HashType getHashType(final Enums.HashType hash) throws GeneralSecurityException {
        switch (hash) {
            case SHA256: {
                return RsaSsaPssParameters.HashType.SHA256;
            }
            case SHA384: {
                return RsaSsaPssParameters.HashType.SHA384;
            }
            case SHA512: {
                return RsaSsaPssParameters.HashType.SHA512;
            }
            default: {
                throw new GeneralSecurityException("Unsupported hash: " + hash);
            }
        }
    }
    
    @AccessesPartialKey
    private RsaSsaPssPublicKey convertKey(final RSAPublicKey pubKey, final Enums.HashType sigHash, final Enums.HashType mgf1Hash, final int saltLength) throws GeneralSecurityException {
        final RsaSsaPssParameters parameters = RsaSsaPssParameters.builder().setModulusSizeBits(pubKey.getModulus().bitLength()).setPublicExponent(pubKey.getPublicExponent()).setSigHashType(getHashType(sigHash)).setMgf1HashType(getHashType(mgf1Hash)).setSaltLengthBytes(saltLength).setVariant(RsaSsaPssParameters.Variant.NO_PREFIX).build();
        return RsaSsaPssPublicKey.builder().setParameters(parameters).setModulus(pubKey.getModulus()).build();
    }
    
    public RsaSsaPssVerifyJce(final RSAPublicKey pubKey, final Enums.HashType sigHash, final Enums.HashType mgf1Hash, final int saltLength) throws GeneralSecurityException {
        this.verify = create(this.convertKey(pubKey, sigHash, mgf1Hash, saltLength));
    }
    
    @Override
    public void verify(final byte[] signature, final byte[] data) throws GeneralSecurityException {
        this.verify.verify(signature, data);
    }
    
    static {
        FIPS = TinkFipsUtil.AlgorithmFipsCompatibility.ALGORITHM_REQUIRES_BORINGCRYPTO;
        HASH_TYPE_CONVERTER = EnumTypeProtoConverter.builder().add(Enums.HashType.SHA256, RsaSsaPssParameters.HashType.SHA256).add(Enums.HashType.SHA384, RsaSsaPssParameters.HashType.SHA384).add(Enums.HashType.SHA512, RsaSsaPssParameters.HashType.SHA512).build();
        EMPTY = new byte[0];
        legacyMessageSuffix = new byte[] { 0 };
    }
    
    private static final class InternalImpl implements PublicKeyVerify
    {
        private final RSAPublicKey publicKey;
        private final Enums.HashType sigHash;
        private final Enums.HashType mgf1Hash;
        private final int saltLength;
        private final byte[] outputPrefix;
        private final byte[] messageSuffix;
        
        private InternalImpl(final RSAPublicKey pubKey, final Enums.HashType sigHash, final Enums.HashType mgf1Hash, final int saltLength, final byte[] outputPrefix, final byte[] messageSuffix) throws GeneralSecurityException {
            if (TinkFipsUtil.useOnlyFips()) {
                throw new GeneralSecurityException("Can not use RSA PSS in FIPS-mode, as BoringCrypto module is not available.");
            }
            Validators.validateSignatureHash(sigHash);
            if (!sigHash.equals(mgf1Hash)) {
                throw new GeneralSecurityException("sigHash and mgf1Hash must be the same");
            }
            Validators.validateRsaModulusSize(pubKey.getModulus().bitLength());
            Validators.validateRsaPublicExponent(pubKey.getPublicExponent());
            this.publicKey = pubKey;
            this.sigHash = sigHash;
            this.mgf1Hash = mgf1Hash;
            this.saltLength = saltLength;
            this.outputPrefix = outputPrefix;
            this.messageSuffix = messageSuffix;
        }
        
        private void noPrefixVerify(final byte[] signature, final byte[] data) throws GeneralSecurityException {
            final BigInteger e = this.publicKey.getPublicExponent();
            final BigInteger n = this.publicKey.getModulus();
            final int nLengthInBytes = (n.bitLength() + 7) / 8;
            final int mLen = (n.bitLength() - 1 + 7) / 8;
            if (nLengthInBytes != signature.length) {
                throw new GeneralSecurityException("invalid signature's length");
            }
            final BigInteger s = SubtleUtil.bytes2Integer(signature);
            if (s.compareTo(n) >= 0) {
                throw new GeneralSecurityException("signature out of range");
            }
            final BigInteger m = s.modPow(e, n);
            final byte[] em = SubtleUtil.integer2Bytes(m, mLen);
            this.emsaPssVerify(data, em, n.bitLength() - 1);
        }
        
        private void emsaPssVerify(final byte[] message, final byte[] em, final int emBits) throws GeneralSecurityException {
            Validators.validateSignatureHash(this.sigHash);
            final MessageDigest digest = EngineFactory.MESSAGE_DIGEST.getInstance(SubtleUtil.toDigestAlgo(this.sigHash));
            digest.update(message);
            if (this.messageSuffix.length != 0) {
                digest.update(this.messageSuffix);
            }
            final byte[] mHash = digest.digest();
            final int hLen = digest.getDigestLength();
            final int emLen = em.length;
            if (emLen < hLen + this.saltLength + 2) {
                throw new GeneralSecurityException("inconsistent");
            }
            if (em[em.length - 1] != -68) {
                throw new GeneralSecurityException("inconsistent");
            }
            final byte[] maskedDb = Arrays.copyOf(em, emLen - hLen - 1);
            final byte[] h = Arrays.copyOfRange(em, maskedDb.length, maskedDb.length + hLen);
            for (int i = 0; i < emLen * 8L - emBits; ++i) {
                final int bytePos = i / 8;
                final int bitPos = 7 - i % 8;
                if ((maskedDb[bytePos] >> bitPos & 0x1) != 0x0) {
                    throw new GeneralSecurityException("inconsistent");
                }
            }
            final byte[] dbMask = SubtleUtil.mgf1(h, emLen - hLen - 1, this.mgf1Hash);
            final byte[] db = new byte[dbMask.length];
            for (int j = 0; j < db.length; ++j) {
                db[j] = (byte)(dbMask[j] ^ maskedDb[j]);
            }
            for (int j = 0; j <= emLen * 8L - emBits; ++j) {
                final int bytePos2 = j / 8;
                final int bitPos2 = 7 - j % 8;
                db[bytePos2] &= (byte)~(1 << bitPos2);
            }
            for (int j = 0; j < emLen - hLen - this.saltLength - 2; ++j) {
                if (db[j] != 0) {
                    throw new GeneralSecurityException("inconsistent");
                }
            }
            if (db[emLen - hLen - this.saltLength - 2] != 1) {
                throw new GeneralSecurityException("inconsistent");
            }
            final byte[] salt = Arrays.copyOfRange(db, db.length - this.saltLength, db.length);
            final byte[] mPrime = new byte[8 + hLen + this.saltLength];
            System.arraycopy(mHash, 0, mPrime, 8, mHash.length);
            System.arraycopy(salt, 0, mPrime, 8 + hLen, salt.length);
            final byte[] hPrime = digest.digest(mPrime);
            if (!Bytes.equal(hPrime, h)) {
                throw new GeneralSecurityException("inconsistent");
            }
        }
        
        @Override
        public void verify(final byte[] signature, final byte[] data) throws GeneralSecurityException {
            if (this.outputPrefix.length == 0) {
                this.noPrefixVerify(signature, data);
                return;
            }
            if (!Util.isPrefix(this.outputPrefix, signature)) {
                throw new GeneralSecurityException("Invalid signature (output prefix mismatch)");
            }
            final byte[] signatureNoPrefix = Arrays.copyOfRange(signature, this.outputPrefix.length, signature.length);
            this.noPrefixVerify(signatureNoPrefix, data);
        }
    }
}
