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

package com.nimbusds.jose.jwk;

import java.io.Serializable;
import java.security.KeyStoreException;
import java.security.Key;
import java.security.cert.Certificate;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateEncodingException;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.text.ParseException;
import java.util.Iterator;
import com.nimbusds.jose.util.JSONObjectUtils;
import com.nimbusds.jose.util.JSONArrayUtils;
import java.util.Map;
import com.nimbusds.jose.util.IntegerOverflowException;
import com.nimbusds.jose.util.ByteUtils;
import java.util.LinkedHashMap;
import java.security.KeyPair;
import java.security.PublicKey;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAMultiPrimePrivateCrtKeySpec;
import java.security.spec.RSAOtherPrimeInfo;
import java.security.spec.RSAPrivateKeySpec;
import java.security.GeneralSecurityException;
import java.math.BigInteger;
import java.security.spec.InvalidKeySpecException;
import java.security.NoSuchAlgorithmException;
import com.nimbusds.jose.JOSEException;
import java.security.spec.KeySpec;
import java.security.KeyFactory;
import java.security.spec.RSAPublicKeySpec;
import java.security.interfaces.RSAMultiPrimePrivateCrtKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Collections;
import java.security.cert.X509Certificate;
import java.util.Objects;
import java.util.Date;
import java.security.KeyStore;
import com.nimbusds.jose.util.Base64;
import java.net.URI;
import com.nimbusds.jose.Algorithm;
import java.util.Set;
import java.security.PrivateKey;
import java.util.List;
import com.nimbusds.jose.util.Base64URL;
import com.nimbusds.jose.shaded.jcip.Immutable;

@Immutable
public final class RSAKey extends JWK implements AsymmetricJWK
{
    private static final long serialVersionUID = 1L;
    private final Base64URL n;
    private final Base64URL e;
    private final Base64URL d;
    private final Base64URL p;
    private final Base64URL q;
    private final Base64URL dp;
    private final Base64URL dq;
    private final Base64URL qi;
    private final List<OtherPrimesInfo> oth;
    private final PrivateKey privateKey;
    
    @Deprecated
    public RSAKey(final Base64URL n, final Base64URL e, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final KeyStore ks) {
        this(n, e, null, null, null, null, null, null, null, null, use, ops, alg, kid, x5u, x5t, x5t256, x5c, null, null, null, ks);
    }
    
    @Deprecated
    public RSAKey(final Base64URL n, final Base64URL e, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final Date exp, final Date nbf, final Date iat, final KeyStore ks) {
        this(n, e, null, null, null, null, null, null, null, null, use, ops, alg, kid, x5u, x5t, x5t256, x5c, exp, nbf, iat, ks);
    }
    
    public RSAKey(final Base64URL n, final Base64URL e, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final Date exp, final Date nbf, final Date iat, final KeyRevocation revocation, final KeyStore ks) {
        this(n, e, null, null, null, null, null, null, null, null, use, ops, alg, kid, x5u, x5t, x5t256, x5c, exp, nbf, iat, revocation, ks);
    }
    
    @Deprecated
    public RSAKey(final Base64URL n, final Base64URL e, final Base64URL d, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final KeyStore ks) {
        this(n, e, d, null, null, null, null, null, null, null, use, ops, alg, kid, x5u, x5t, x5t256, x5c, null, null, null, ks);
    }
    
    @Deprecated
    public RSAKey(final Base64URL n, final Base64URL e, final Base64URL d, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final Date exp, final Date nbf, final Date iat, final KeyStore ks) {
        this(n, e, d, use, ops, alg, kid, x5u, x5t, x5t256, x5c, exp, nbf, iat, null, ks);
    }
    
    public RSAKey(final Base64URL n, final Base64URL e, final Base64URL d, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final Date exp, final Date nbf, final Date iat, final KeyRevocation revocation, final KeyStore ks) {
        this(n, e, d, null, null, null, null, null, null, null, use, ops, alg, kid, x5u, x5t, x5t256, x5c, exp, nbf, iat, revocation, ks);
        Objects.requireNonNull(d, "The private exponent must not be null");
    }
    
    @Deprecated
    public RSAKey(final Base64URL n, final Base64URL e, final Base64URL p, final Base64URL q, final Base64URL dp, final Base64URL dq, final Base64URL qi, final List<OtherPrimesInfo> oth, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final KeyStore ks) {
        this(n, e, null, p, q, dp, dq, qi, oth, null, use, ops, alg, kid, x5u, x5t, x5t256, x5c, null, null, null, ks);
    }
    
    @Deprecated
    public RSAKey(final Base64URL n, final Base64URL e, final Base64URL p, final Base64URL q, final Base64URL dp, final Base64URL dq, final Base64URL qi, final List<OtherPrimesInfo> oth, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final Date exp, final Date nbf, final Date iat, final KeyStore ks) {
        this(n, e, p, q, dp, dq, qi, oth, use, ops, alg, kid, x5u, x5t, x5t256, x5c, exp, nbf, iat, null, ks);
    }
    
    public RSAKey(final Base64URL n, final Base64URL e, final Base64URL p, final Base64URL q, final Base64URL dp, final Base64URL dq, final Base64URL qi, final List<OtherPrimesInfo> oth, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final Date exp, final Date nbf, final Date iat, final KeyRevocation revocation, final KeyStore ks) {
        this(n, e, null, p, q, dp, dq, qi, oth, null, use, ops, alg, kid, x5u, x5t, x5t256, x5c, exp, nbf, iat, revocation, ks);
        Objects.requireNonNull(p, "The first prime factor must not be null");
        Objects.requireNonNull(q, "The second prime factor must not be null");
        Objects.requireNonNull(dp, "The first factor CRT exponent must not be null");
        Objects.requireNonNull(dq, "The second factor CRT exponent must not be null");
        Objects.requireNonNull(qi, "The first CRT coefficient must not be null");
    }
    
    @Deprecated
    public RSAKey(final Base64URL n, final Base64URL e, final Base64URL d, final Base64URL p, final Base64URL q, final Base64URL dp, final Base64URL dq, final Base64URL qi, final List<OtherPrimesInfo> oth, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c) {
        this(n, e, d, p, q, dp, dq, qi, oth, null, use, ops, alg, kid, x5u, x5t, x5t256, x5c, null);
    }
    
    @Deprecated
    public RSAKey(final Base64URL n, final Base64URL e, final Base64URL d, final Base64URL p, final Base64URL q, final Base64URL dp, final Base64URL dq, final Base64URL qi, final List<OtherPrimesInfo> oth, final PrivateKey prv, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final KeyStore ks) {
        this(n, e, d, p, q, dp, dq, qi, oth, prv, use, ops, alg, kid, x5u, x5t, x5t256, x5c, null, null, null, ks);
    }
    
    @Deprecated
    public RSAKey(final Base64URL n, final Base64URL e, final Base64URL d, final Base64URL p, final Base64URL q, final Base64URL dp, final Base64URL dq, final Base64URL qi, final List<OtherPrimesInfo> oth, final PrivateKey prv, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final Date exp, final Date nbf, final Date iat, final KeyStore ks) {
        this(n, e, d, p, q, dp, dq, qi, oth, prv, use, ops, alg, kid, x5u, x5t, x5t256, x5c, exp, nbf, iat, null, ks);
    }
    
    public RSAKey(final Base64URL n, final Base64URL e, final Base64URL d, final Base64URL p, final Base64URL q, final Base64URL dp, final Base64URL dq, final Base64URL qi, final List<OtherPrimesInfo> oth, final PrivateKey prv, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final Date exp, final Date nbf, final Date iat, final KeyRevocation revocation, final KeyStore ks) {
        super(KeyType.RSA, use, ops, alg, kid, x5u, x5t, x5t256, x5c, exp, nbf, iat, revocation, ks);
        this.n = Objects.requireNonNull(n, "The modulus value must not be null");
        this.e = Objects.requireNonNull(e, "The public exponent value must not be null");
        if (this.getParsedX509CertChain() != null && !this.matches(this.getParsedX509CertChain().get(0))) {
            throw new IllegalArgumentException("The public subject key info of the first X.509 certificate in the chain must match the JWK type and public parameters");
        }
        this.d = d;
        if (p != null && q != null && dp != null && dq != null && qi != null) {
            this.p = p;
            this.q = q;
            this.dp = dp;
            this.dq = dq;
            this.qi = qi;
            if (oth != null) {
                this.oth = Collections.unmodifiableList((List<? extends OtherPrimesInfo>)oth);
            }
            else {
                this.oth = Collections.emptyList();
            }
        }
        else if (p == null && q == null && dp == null && dq == null && qi == null && oth == null) {
            this.p = null;
            this.q = null;
            this.dp = null;
            this.dq = null;
            this.qi = null;
            this.oth = Collections.emptyList();
        }
        else {
            if (p != null || q != null || dp != null || dq != null || qi != null) {
                Objects.requireNonNull(p, "Incomplete second private (CRT) representation: The first prime factor must not be null");
                Objects.requireNonNull(q, "Incomplete second private (CRT) representation: The second prime factor must not be null");
                Objects.requireNonNull(dp, "Incomplete second private (CRT) representation: The first factor CRT exponent must not be null");
                Objects.requireNonNull(dq, "Incomplete second private (CRT) representation: The second factor CRT exponent must not be null");
                throw new IllegalArgumentException("Incomplete second private (CRT) representation: The first CRT coefficient must not be null");
            }
            this.p = null;
            this.q = null;
            this.dp = null;
            this.dq = null;
            this.qi = null;
            this.oth = Collections.emptyList();
        }
        this.privateKey = prv;
    }
    
    @Deprecated
    public RSAKey(final RSAPublicKey pub, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final KeyStore ks) {
        this(pub, use, ops, alg, kid, x5u, x5t, x5t256, x5c, null, null, null, ks);
    }
    
    @Deprecated
    public RSAKey(final RSAPublicKey pub, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final Date exp, final Date nbf, final Date iat, final KeyStore ks) {
        this(pub, use, ops, alg, kid, x5u, x5t, x5t256, x5c, exp, nbf, iat, null, ks);
    }
    
    public RSAKey(final RSAPublicKey pub, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final Date exp, final Date nbf, final Date iat, final KeyRevocation revocation, final KeyStore ks) {
        this(Base64URL.encode(pub.getModulus()), Base64URL.encode(pub.getPublicExponent()), use, ops, alg, kid, x5u, x5t, x5t256, x5c, exp, nbf, iat, revocation, ks);
    }
    
    @Deprecated
    public RSAKey(final RSAPublicKey pub, final RSAPrivateKey priv, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final KeyStore ks) {
        this(pub, priv, use, ops, alg, kid, x5u, x5t, x5t256, x5c, null, null, null, ks);
    }
    
    @Deprecated
    public RSAKey(final RSAPublicKey pub, final RSAPrivateKey priv, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final Date exp, final Date nbf, final Date iat, final KeyStore ks) {
        this(pub, priv, use, ops, alg, kid, x5u, x5t, x5t256, x5c, exp, nbf, iat, null, ks);
    }
    
    public RSAKey(final RSAPublicKey pub, final RSAPrivateKey priv, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final Date exp, final Date nbf, final Date iat, final KeyRevocation revocation, final KeyStore ks) {
        this(Base64URL.encode(pub.getModulus()), Base64URL.encode(pub.getPublicExponent()), Base64URL.encode(priv.getPrivateExponent()), use, ops, alg, kid, x5u, x5t, x5t256, x5c, exp, nbf, iat, revocation, ks);
    }
    
    @Deprecated
    public RSAKey(final RSAPublicKey pub, final RSAPrivateCrtKey priv, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final KeyStore ks) {
        this(pub, priv, use, ops, alg, kid, x5u, x5t, x5t256, x5c, null, null, null, ks);
    }
    
    @Deprecated
    public RSAKey(final RSAPublicKey pub, final RSAPrivateCrtKey priv, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final Date exp, final Date nbf, final Date iat, final KeyStore ks) {
        this(Base64URL.encode(pub.getModulus()), Base64URL.encode(pub.getPublicExponent()), Base64URL.encode(priv.getPrivateExponent()), Base64URL.encode(priv.getPrimeP()), Base64URL.encode(priv.getPrimeQ()), Base64URL.encode(priv.getPrimeExponentP()), Base64URL.encode(priv.getPrimeExponentQ()), Base64URL.encode(priv.getCrtCoefficient()), null, null, use, ops, alg, kid, x5u, x5t, x5t256, x5c, exp, nbf, iat, ks);
    }
    
    public RSAKey(final RSAPublicKey pub, final RSAPrivateCrtKey priv, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final Date exp, final Date nbf, final Date iat, final KeyRevocation revocation, final KeyStore ks) {
        this(Base64URL.encode(pub.getModulus()), Base64URL.encode(pub.getPublicExponent()), Base64URL.encode(priv.getPrivateExponent()), Base64URL.encode(priv.getPrimeP()), Base64URL.encode(priv.getPrimeQ()), Base64URL.encode(priv.getPrimeExponentP()), Base64URL.encode(priv.getPrimeExponentQ()), Base64URL.encode(priv.getCrtCoefficient()), null, null, use, ops, alg, kid, x5u, x5t, x5t256, x5c, exp, nbf, iat, revocation, ks);
    }
    
    @Deprecated
    public RSAKey(final RSAPublicKey pub, final RSAMultiPrimePrivateCrtKey priv, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final KeyStore ks) {
        this(pub, priv, use, ops, alg, kid, x5u, x5t, x5t256, x5c, null, null, null, ks);
    }
    
    @Deprecated
    public RSAKey(final RSAPublicKey pub, final RSAMultiPrimePrivateCrtKey priv, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final Date exp, final Date nbf, final Date iat, final KeyStore ks) {
        this(Base64URL.encode(pub.getModulus()), Base64URL.encode(pub.getPublicExponent()), Base64URL.encode(priv.getPrivateExponent()), Base64URL.encode(priv.getPrimeP()), Base64URL.encode(priv.getPrimeQ()), Base64URL.encode(priv.getPrimeExponentP()), Base64URL.encode(priv.getPrimeExponentQ()), Base64URL.encode(priv.getCrtCoefficient()), OtherPrimesInfo.toList(priv.getOtherPrimeInfo()), null, use, ops, alg, kid, x5u, x5t, x5t256, x5c, exp, nbf, iat, ks);
    }
    
    public RSAKey(final RSAPublicKey pub, final RSAMultiPrimePrivateCrtKey priv, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final Date exp, final Date nbf, final Date iat, final KeyRevocation revocation, final KeyStore ks) {
        this(Base64URL.encode(pub.getModulus()), Base64URL.encode(pub.getPublicExponent()), Base64URL.encode(priv.getPrivateExponent()), Base64URL.encode(priv.getPrimeP()), Base64URL.encode(priv.getPrimeQ()), Base64URL.encode(priv.getPrimeExponentP()), Base64URL.encode(priv.getPrimeExponentQ()), Base64URL.encode(priv.getCrtCoefficient()), OtherPrimesInfo.toList(priv.getOtherPrimeInfo()), null, use, ops, alg, kid, x5u, x5t, x5t256, x5c, exp, nbf, iat, revocation, ks);
    }
    
    @Deprecated
    public RSAKey(final RSAPublicKey pub, final PrivateKey priv, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final KeyStore ks) {
        this(pub, priv, use, ops, alg, kid, x5u, x5t, x5t256, x5c, null, null, null, ks);
    }
    
    @Deprecated
    public RSAKey(final RSAPublicKey pub, final PrivateKey priv, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final Date exp, final Date nbf, final Date iat, final KeyStore ks) {
        this(pub, priv, use, ops, alg, kid, x5u, x5t, x5t256, x5c, exp, nbf, iat, null, ks);
    }
    
    public RSAKey(final RSAPublicKey pub, final PrivateKey priv, final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, final Date exp, final Date nbf, final Date iat, final KeyRevocation revocation, final KeyStore ks) {
        this(Base64URL.encode(pub.getModulus()), Base64URL.encode(pub.getPublicExponent()), null, null, null, null, null, null, null, priv, use, ops, alg, kid, x5u, x5t, x5t256, x5c, exp, nbf, iat, revocation, ks);
    }
    
    public Base64URL getModulus() {
        return this.n;
    }
    
    public Base64URL getPublicExponent() {
        return this.e;
    }
    
    public Base64URL getPrivateExponent() {
        return this.d;
    }
    
    public Base64URL getFirstPrimeFactor() {
        return this.p;
    }
    
    public Base64URL getSecondPrimeFactor() {
        return this.q;
    }
    
    public Base64URL getFirstFactorCRTExponent() {
        return this.dp;
    }
    
    public Base64URL getSecondFactorCRTExponent() {
        return this.dq;
    }
    
    public Base64URL getFirstCRTCoefficient() {
        return this.qi;
    }
    
    public List<OtherPrimesInfo> getOtherPrimes() {
        return this.oth;
    }
    
    public RSAPublicKey toRSAPublicKey() throws JOSEException {
        final BigInteger modulus = this.n.decodeToBigInteger();
        final BigInteger exponent = this.e.decodeToBigInteger();
        final RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent);
        try {
            final KeyFactory factory = KeyFactory.getInstance("RSA");
            return (RSAPublicKey)factory.generatePublic(spec);
        }
        catch (final NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new JOSEException(e.getMessage(), e);
        }
    }
    
    public RSAPrivateKey toRSAPrivateKey() throws JOSEException {
        if (this.d == null) {
            return null;
        }
        final BigInteger modulus = this.n.decodeToBigInteger();
        final BigInteger privateExponent = this.d.decodeToBigInteger();
        RSAPrivateKeySpec spec;
        if (this.p == null) {
            spec = new RSAPrivateKeySpec(modulus, privateExponent);
        }
        else {
            final BigInteger publicExponent = this.e.decodeToBigInteger();
            final BigInteger primeP = this.p.decodeToBigInteger();
            final BigInteger primeQ = this.q.decodeToBigInteger();
            final BigInteger primeExponentP = this.dp.decodeToBigInteger();
            final BigInteger primeExponentQ = this.dq.decodeToBigInteger();
            final BigInteger crtCoefficient = this.qi.decodeToBigInteger();
            if (this.oth != null && !this.oth.isEmpty()) {
                final RSAOtherPrimeInfo[] otherInfo = new RSAOtherPrimeInfo[this.oth.size()];
                for (int i = 0; i < this.oth.size(); ++i) {
                    final OtherPrimesInfo opi = this.oth.get(i);
                    final BigInteger otherPrime = opi.getPrimeFactor().decodeToBigInteger();
                    final BigInteger otherPrimeExponent = opi.getFactorCRTExponent().decodeToBigInteger();
                    final BigInteger otherCrtCoefficient = opi.getFactorCRTCoefficient().decodeToBigInteger();
                    otherInfo[i] = new RSAOtherPrimeInfo(otherPrime, otherPrimeExponent, otherCrtCoefficient);
                }
                spec = new RSAMultiPrimePrivateCrtKeySpec(modulus, publicExponent, privateExponent, primeP, primeQ, primeExponentP, primeExponentQ, crtCoefficient, otherInfo);
            }
            else {
                spec = new RSAPrivateCrtKeySpec(modulus, publicExponent, privateExponent, primeP, primeQ, primeExponentP, primeExponentQ, crtCoefficient);
            }
        }
        try {
            final KeyFactory factory = KeyFactory.getInstance("RSA");
            return (RSAPrivateKey)factory.generatePrivate(spec);
        }
        catch (final InvalidKeySpecException | NoSuchAlgorithmException e) {
            throw new JOSEException(e.getMessage(), e);
        }
    }
    
    @Override
    public PublicKey toPublicKey() throws JOSEException {
        return this.toRSAPublicKey();
    }
    
    @Override
    public PrivateKey toPrivateKey() throws JOSEException {
        final PrivateKey prv = this.toRSAPrivateKey();
        if (prv != null) {
            return prv;
        }
        return this.privateKey;
    }
    
    @Override
    public KeyPair toKeyPair() throws JOSEException {
        return new KeyPair(this.toRSAPublicKey(), this.toPrivateKey());
    }
    
    @Override
    public RSAKey toRevokedJWK(final KeyRevocation keyRevocation) {
        if (this.getKeyRevocation() != null) {
            throw new IllegalStateException("Already revoked");
        }
        return new Builder(this).keyRevocation(Objects.requireNonNull(keyRevocation)).build();
    }
    
    @Override
    public boolean matches(final X509Certificate cert) {
        RSAPublicKey certRSAKey;
        try {
            certRSAKey = (RSAPublicKey)this.getParsedX509CertChain().get(0).getPublicKey();
        }
        catch (final ClassCastException ex) {
            return false;
        }
        return this.e.decodeToBigInteger().equals(certRSAKey.getPublicExponent()) && this.n.decodeToBigInteger().equals(certRSAKey.getModulus());
    }
    
    @Override
    public LinkedHashMap<String, ?> getRequiredParams() {
        final LinkedHashMap<String, String> requiredParams = new LinkedHashMap<String, String>();
        requiredParams.put("e", this.e.toString());
        requiredParams.put("kty", this.getKeyType().getValue());
        requiredParams.put("n", this.n.toString());
        return requiredParams;
    }
    
    @Override
    public boolean isPrivate() {
        return this.d != null || this.p != null || this.privateKey != null;
    }
    
    @Override
    public int size() {
        try {
            return ByteUtils.safeBitLength(this.n.decode());
        }
        catch (final IntegerOverflowException e) {
            throw new ArithmeticException(e.getMessage());
        }
    }
    
    @Override
    public RSAKey toPublicJWK() {
        return new RSAKey(this.getModulus(), this.getPublicExponent(), this.getKeyUse(), this.getKeyOperations(), this.getAlgorithm(), this.getKeyID(), this.getX509CertURL(), this.getX509CertThumbprint(), this.getX509CertSHA256Thumbprint(), this.getX509CertChain(), this.getExpirationTime(), this.getNotBeforeTime(), this.getIssueTime(), this.getKeyRevocation(), this.getKeyStore());
    }
    
    @Override
    public Map<String, Object> toJSONObject() {
        final Map<String, Object> o = super.toJSONObject();
        o.put("n", this.n.toString());
        o.put("e", this.e.toString());
        if (this.d != null) {
            o.put("d", this.d.toString());
        }
        if (this.p != null) {
            o.put("p", this.p.toString());
        }
        if (this.q != null) {
            o.put("q", this.q.toString());
        }
        if (this.dp != null) {
            o.put("dp", this.dp.toString());
        }
        if (this.dq != null) {
            o.put("dq", this.dq.toString());
        }
        if (this.qi != null) {
            o.put("qi", this.qi.toString());
        }
        if (this.oth != null && !this.oth.isEmpty()) {
            final List<Object> a = JSONArrayUtils.newJSONArray();
            for (final OtherPrimesInfo other : this.oth) {
                final Map<String, Object> oo = JSONObjectUtils.newJSONObject();
                oo.put("r", other.r.toString());
                oo.put("d", other.d.toString());
                oo.put("t", other.t.toString());
                a.add(oo);
            }
            o.put("oth", a);
        }
        return o;
    }
    
    public static RSAKey parse(final String s) throws ParseException {
        return parse(JSONObjectUtils.parse(s));
    }
    
    public static RSAKey parse(final Map<String, Object> jsonObject) throws ParseException {
        if (!KeyType.RSA.equals(JWKMetadata.parseKeyType(jsonObject))) {
            throw new ParseException("The key type \"kty\" must be RSA", 0);
        }
        final Base64URL n = JSONObjectUtils.getBase64URL(jsonObject, "n");
        final Base64URL e = JSONObjectUtils.getBase64URL(jsonObject, "e");
        final Base64URL d = JSONObjectUtils.getBase64URL(jsonObject, "d");
        final Base64URL p = JSONObjectUtils.getBase64URL(jsonObject, "p");
        final Base64URL q = JSONObjectUtils.getBase64URL(jsonObject, "q");
        final Base64URL dp = JSONObjectUtils.getBase64URL(jsonObject, "dp");
        final Base64URL dq = JSONObjectUtils.getBase64URL(jsonObject, "dq");
        final Base64URL qi = JSONObjectUtils.getBase64URL(jsonObject, "qi");
        List<OtherPrimesInfo> oth = null;
        if (jsonObject.containsKey("oth")) {
            final List<Object> arr = JSONObjectUtils.getJSONArray(jsonObject, "oth");
            if (arr != null) {
                oth = new ArrayList<OtherPrimesInfo>(arr.size());
                for (final Object o : arr) {
                    if (o instanceof Map) {
                        final Map<String, Object> otherJson = (Map<String, Object>)o;
                        final Base64URL r = JSONObjectUtils.getBase64URL(otherJson, "r");
                        final Base64URL odq = JSONObjectUtils.getBase64URL(otherJson, "dq");
                        final Base64URL t = JSONObjectUtils.getBase64URL(otherJson, "t");
                        try {
                            oth.add(new OtherPrimesInfo(r, odq, t));
                        }
                        catch (final IllegalArgumentException iae) {
                            throw new ParseException(iae.getMessage(), 0);
                        }
                    }
                }
            }
        }
        try {
            return new RSAKey(n, e, d, p, q, dp, dq, qi, oth, null, JWKMetadata.parseKeyUse(jsonObject), JWKMetadata.parseKeyOperations(jsonObject), JWKMetadata.parseAlgorithm(jsonObject), JWKMetadata.parseKeyID(jsonObject), JWKMetadata.parseX509CertURL(jsonObject), JWKMetadata.parseX509CertThumbprint(jsonObject), JWKMetadata.parseX509CertSHA256Thumbprint(jsonObject), JWKMetadata.parseX509CertChain(jsonObject), JWKMetadata.parseExpirationTime(jsonObject), JWKMetadata.parseNotBeforeTime(jsonObject), JWKMetadata.parseIssueTime(jsonObject), JWKMetadata.parseKeyRevocation(jsonObject), null);
        }
        catch (final Exception ex) {
            throw new ParseException(ex.getMessage(), 0);
        }
    }
    
    public static RSAKey parse(final X509Certificate cert) throws JOSEException {
        if (!(cert.getPublicKey() instanceof RSAPublicKey)) {
            throw new JOSEException("The public key of the X.509 certificate is not RSA");
        }
        final RSAPublicKey publicKey = (RSAPublicKey)cert.getPublicKey();
        try {
            final MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
            return new Builder(publicKey).keyUse(KeyUse.from(cert)).keyID(cert.getSerialNumber().toString(10)).x509CertChain(Collections.singletonList(Base64.encode(cert.getEncoded()))).x509CertSHA256Thumbprint(Base64URL.encode(sha256.digest(cert.getEncoded()))).expirationTime(cert.getNotAfter()).notBeforeTime(cert.getNotBefore()).build();
        }
        catch (final NoSuchAlgorithmException e) {
            throw new JOSEException("Couldn't encode x5t parameter: " + e.getMessage(), e);
        }
        catch (final CertificateEncodingException e2) {
            throw new JOSEException("Couldn't encode x5c parameter: " + e2.getMessage(), e2);
        }
    }
    
    public static RSAKey load(final KeyStore keyStore, final String alias, final char[] pin) throws KeyStoreException, JOSEException {
        final Certificate cert = keyStore.getCertificate(alias);
        if (!(cert instanceof X509Certificate)) {
            return null;
        }
        final X509Certificate x509Cert = (X509Certificate)cert;
        if (!(x509Cert.getPublicKey() instanceof RSAPublicKey)) {
            throw new JOSEException("Couldn't load RSA JWK: The key algorithm is not RSA");
        }
        RSAKey rsaJWK = parse(x509Cert);
        rsaJWK = new Builder(rsaJWK).keyID(alias).keyStore(keyStore).build();
        Key key;
        try {
            key = keyStore.getKey(alias, pin);
        }
        catch (final UnrecoverableKeyException | NoSuchAlgorithmException e) {
            throw new JOSEException("Couldn't retrieve private RSA key (bad pin?): " + e.getMessage(), e);
        }
        if (key instanceof RSAPrivateKey) {
            return new Builder(rsaJWK).privateKey((RSAPrivateKey)key).build();
        }
        if (key instanceof PrivateKey && "RSA".equalsIgnoreCase(key.getAlgorithm())) {
            return new Builder(rsaJWK).privateKey((PrivateKey)key).build();
        }
        return rsaJWK;
    }
    
    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof RSAKey)) {
            return false;
        }
        if (!super.equals(o)) {
            return false;
        }
        final RSAKey rsaKey = (RSAKey)o;
        return Objects.equals(this.n, rsaKey.n) && Objects.equals(this.e, rsaKey.e) && Objects.equals(this.d, rsaKey.d) && Objects.equals(this.p, rsaKey.p) && Objects.equals(this.q, rsaKey.q) && Objects.equals(this.dp, rsaKey.dp) && Objects.equals(this.dq, rsaKey.dq) && Objects.equals(this.qi, rsaKey.qi) && Objects.equals(this.oth, rsaKey.oth) && Objects.equals(this.privateKey, rsaKey.privateKey);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode(), this.n, this.e, this.d, this.p, this.q, this.dp, this.dq, this.qi, this.oth, this.privateKey);
    }
    
    @Immutable
    public static class OtherPrimesInfo implements Serializable
    {
        private static final long serialVersionUID = 1L;
        private final Base64URL r;
        private final Base64URL d;
        private final Base64URL t;
        
        public OtherPrimesInfo(final Base64URL r, final Base64URL d, final Base64URL t) {
            this.r = Objects.requireNonNull(r);
            this.d = Objects.requireNonNull(d);
            this.t = Objects.requireNonNull(t);
        }
        
        public OtherPrimesInfo(final RSAOtherPrimeInfo oth) {
            this.r = Base64URL.encode(oth.getPrime());
            this.d = Base64URL.encode(oth.getExponent());
            this.t = Base64URL.encode(oth.getCrtCoefficient());
        }
        
        public Base64URL getPrimeFactor() {
            return this.r;
        }
        
        public Base64URL getFactorCRTExponent() {
            return this.d;
        }
        
        public Base64URL getFactorCRTCoefficient() {
            return this.t;
        }
        
        public static List<OtherPrimesInfo> toList(final RSAOtherPrimeInfo[] othArray) {
            final List<OtherPrimesInfo> list = new ArrayList<OtherPrimesInfo>();
            if (othArray == null) {
                return list;
            }
            for (final RSAOtherPrimeInfo oth : othArray) {
                list.add(new OtherPrimesInfo(oth));
            }
            return list;
        }
    }
    
    public static class Builder
    {
        private final Base64URL n;
        private final Base64URL e;
        private Base64URL d;
        private Base64URL p;
        private Base64URL q;
        private Base64URL dp;
        private Base64URL dq;
        private Base64URL qi;
        private List<OtherPrimesInfo> oth;
        private PrivateKey priv;
        private KeyUse use;
        private Set<KeyOperation> ops;
        private Algorithm alg;
        private String kid;
        private URI x5u;
        @Deprecated
        private Base64URL x5t;
        private Base64URL x5t256;
        private List<Base64> x5c;
        private Date exp;
        private Date nbf;
        private Date iat;
        private KeyRevocation revocation;
        private KeyStore ks;
        
        public Builder(final Base64URL n, final Base64URL e) {
            this.n = Objects.requireNonNull(n);
            this.e = Objects.requireNonNull(e);
        }
        
        public Builder(final RSAPublicKey pub) {
            this.n = Base64URL.encode(pub.getModulus());
            this.e = Base64URL.encode(pub.getPublicExponent());
        }
        
        public Builder(final RSAKey rsaJWK) {
            this.n = rsaJWK.n;
            this.e = rsaJWK.e;
            this.d = rsaJWK.d;
            this.p = rsaJWK.p;
            this.q = rsaJWK.q;
            this.dp = rsaJWK.dp;
            this.dq = rsaJWK.dq;
            this.qi = rsaJWK.qi;
            this.oth = rsaJWK.oth;
            this.priv = rsaJWK.privateKey;
            this.use = rsaJWK.getKeyUse();
            this.ops = rsaJWK.getKeyOperations();
            this.alg = rsaJWK.getAlgorithm();
            this.kid = rsaJWK.getKeyID();
            this.x5u = rsaJWK.getX509CertURL();
            this.x5t = rsaJWK.getX509CertThumbprint();
            this.x5t256 = rsaJWK.getX509CertSHA256Thumbprint();
            this.x5c = rsaJWK.getX509CertChain();
            this.exp = rsaJWK.getExpirationTime();
            this.nbf = rsaJWK.getNotBeforeTime();
            this.iat = rsaJWK.getIssueTime();
            this.revocation = rsaJWK.getKeyRevocation();
            this.ks = rsaJWK.getKeyStore();
        }
        
        public Builder privateExponent(final Base64URL d) {
            this.d = d;
            return this;
        }
        
        public Builder privateKey(final RSAPrivateKey priv) {
            if (priv instanceof RSAPrivateCrtKey) {
                return this.privateKey((RSAPrivateCrtKey)priv);
            }
            if (priv instanceof RSAMultiPrimePrivateCrtKey) {
                return this.privateKey((RSAMultiPrimePrivateCrtKey)priv);
            }
            if (priv != null) {
                this.d = Base64URL.encode(priv.getPrivateExponent());
            }
            else {
                this.d = null;
            }
            return this;
        }
        
        public Builder privateKey(final PrivateKey priv) {
            if (priv instanceof RSAPrivateKey) {
                return this.privateKey((RSAPrivateKey)priv);
            }
            if (priv != null && !"RSA".equalsIgnoreCase(priv.getAlgorithm())) {
                throw new IllegalArgumentException("The private key algorithm must be RSA");
            }
            this.priv = priv;
            return this;
        }
        
        public Builder firstPrimeFactor(final Base64URL p) {
            this.p = p;
            return this;
        }
        
        public Builder secondPrimeFactor(final Base64URL q) {
            this.q = q;
            return this;
        }
        
        public Builder firstFactorCRTExponent(final Base64URL dp) {
            this.dp = dp;
            return this;
        }
        
        public Builder secondFactorCRTExponent(final Base64URL dq) {
            this.dq = dq;
            return this;
        }
        
        public Builder firstCRTCoefficient(final Base64URL qi) {
            this.qi = qi;
            return this;
        }
        
        public Builder otherPrimes(final List<OtherPrimesInfo> oth) {
            this.oth = oth;
            return this;
        }
        
        public Builder privateKey(final RSAPrivateCrtKey priv) {
            if (priv != null) {
                this.d = Base64URL.encode(priv.getPrivateExponent());
                this.p = Base64URL.encode(priv.getPrimeP());
                this.q = Base64URL.encode(priv.getPrimeQ());
                this.dp = Base64URL.encode(priv.getPrimeExponentP());
                this.dq = Base64URL.encode(priv.getPrimeExponentQ());
                this.qi = Base64URL.encode(priv.getCrtCoefficient());
            }
            else {
                this.d = null;
                this.p = null;
                this.q = null;
                this.dp = null;
                this.dq = null;
                this.qi = null;
            }
            return this;
        }
        
        public Builder privateKey(final RSAMultiPrimePrivateCrtKey priv) {
            if (priv != null) {
                this.d = Base64URL.encode(priv.getPrivateExponent());
                this.p = Base64URL.encode(priv.getPrimeP());
                this.q = Base64URL.encode(priv.getPrimeQ());
                this.dp = Base64URL.encode(priv.getPrimeExponentP());
                this.dq = Base64URL.encode(priv.getPrimeExponentQ());
                this.qi = Base64URL.encode(priv.getCrtCoefficient());
                this.oth = OtherPrimesInfo.toList(priv.getOtherPrimeInfo());
            }
            else {
                this.d = null;
                this.p = null;
                this.q = null;
                this.dp = null;
                this.dq = null;
                this.qi = null;
                this.oth = null;
            }
            return this;
        }
        
        public Builder keyUse(final KeyUse use) {
            this.use = use;
            return this;
        }
        
        public Builder keyOperations(final Set<KeyOperation> ops) {
            this.ops = ops;
            return this;
        }
        
        public Builder algorithm(final Algorithm alg) {
            this.alg = alg;
            return this;
        }
        
        public Builder keyID(final String kid) {
            this.kid = kid;
            return this;
        }
        
        public Builder keyIDFromThumbprint() throws JOSEException {
            return this.keyIDFromThumbprint("SHA-256");
        }
        
        public Builder keyIDFromThumbprint(final String hashAlg) throws JOSEException {
            final LinkedHashMap<String, Object> requiredParams = new LinkedHashMap<String, Object>();
            requiredParams.put("e", this.e.toString());
            requiredParams.put("kty", KeyType.RSA.getValue());
            requiredParams.put("n", this.n.toString());
            this.kid = ThumbprintUtils.compute(hashAlg, requiredParams).toString();
            return this;
        }
        
        public Builder x509CertURL(final URI x5u) {
            this.x5u = x5u;
            return this;
        }
        
        @Deprecated
        public Builder x509CertThumbprint(final Base64URL x5t) {
            this.x5t = x5t;
            return this;
        }
        
        public Builder x509CertSHA256Thumbprint(final Base64URL x5t256) {
            this.x5t256 = x5t256;
            return this;
        }
        
        public Builder x509CertChain(final List<Base64> x5c) {
            this.x5c = x5c;
            return this;
        }
        
        public Builder expirationTime(final Date exp) {
            this.exp = exp;
            return this;
        }
        
        public Builder notBeforeTime(final Date nbf) {
            this.nbf = nbf;
            return this;
        }
        
        public Builder issueTime(final Date iat) {
            this.iat = iat;
            return this;
        }
        
        public Builder keyRevocation(final KeyRevocation revocation) {
            this.revocation = revocation;
            return this;
        }
        
        public Builder keyStore(final KeyStore keyStore) {
            this.ks = keyStore;
            return this;
        }
        
        public RSAKey build() {
            try {
                return new RSAKey(this.n, this.e, this.d, this.p, this.q, this.dp, this.dq, this.qi, this.oth, this.priv, this.use, this.ops, this.alg, this.kid, this.x5u, this.x5t, this.x5t256, this.x5c, this.exp, this.nbf, this.iat, this.revocation, this.ks);
            }
            catch (final IllegalArgumentException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
        }
    }
}
