1package org.bouncycastle.jcajce.provider.asymmetric.dh;
2
3import java.math.BigInteger;
4import java.security.InvalidAlgorithmParameterException;
5import java.security.InvalidKeyException;
6import java.security.Key;
7import java.security.NoSuchAlgorithmException;
8import java.security.SecureRandom;
9import java.security.spec.AlgorithmParameterSpec;
10
11import javax.crypto.SecretKey;
12import javax.crypto.ShortBufferException;
13import javax.crypto.interfaces.DHPrivateKey;
14import javax.crypto.interfaces.DHPublicKey;
15import javax.crypto.spec.DHParameterSpec;
16import javax.crypto.spec.SecretKeySpec;
17
18import org.bouncycastle.crypto.DerivationFunction;
19// BEGIN android-removed
20// import org.bouncycastle.crypto.agreement.kdf.DHKEKGenerator;
21// import org.bouncycastle.crypto.util.DigestFactory;
22// END android-removed
23import org.bouncycastle.jcajce.provider.asymmetric.util.BaseAgreementSpi;
24import org.bouncycastle.jcajce.spec.UserKeyingMaterialSpec;
25
26/**
27 * Diffie-Hellman key agreement. There's actually a better way of doing this
28 * if you are using long term public keys, see the light-weight version for
29 * details.
30 */
31public class KeyAgreementSpi
32    extends BaseAgreementSpi
33{
34    private static final BigInteger ONE = BigInteger.valueOf(1);
35    private static final BigInteger TWO = BigInteger.valueOf(2);
36
37    private BigInteger      x;
38    private BigInteger      p;
39    private BigInteger      g;
40
41    private BigInteger     result;
42
43    public KeyAgreementSpi()
44    {
45        super("Diffie-Hellman", null);
46    }
47
48    public KeyAgreementSpi(
49        String kaAlgorithm,
50        DerivationFunction kdf)
51    {
52        super(kaAlgorithm, kdf);
53    }
54
55    protected byte[] bigIntToBytes(
56        BigInteger    r)
57    {
58        //
59        // RFC 2631 (2.1.2) specifies that the secret should be padded with leading zeros if necessary
60        // must be the same length as p
61        //
62        int expectedLength = (p.bitLength() + 7) / 8;
63
64        byte[]    tmp = r.toByteArray();
65
66        if (tmp.length == expectedLength)
67        {
68            return tmp;
69        }
70
71        if (tmp[0] == 0 && tmp.length == expectedLength + 1)
72        {
73            byte[]    rv = new byte[tmp.length - 1];
74
75            System.arraycopy(tmp, 1, rv, 0, rv.length);
76            return rv;
77        }
78
79        // tmp must be shorter than expectedLength
80        // pad to the left with zeros.
81        byte[]    rv = new byte[expectedLength];
82
83        System.arraycopy(tmp, 0, rv, rv.length - tmp.length, tmp.length);
84
85        return rv;
86    }
87
88    protected Key engineDoPhase(
89        Key     key,
90        boolean lastPhase)
91        throws InvalidKeyException, IllegalStateException
92    {
93        if (x == null)
94        {
95            throw new IllegalStateException("Diffie-Hellman not initialised.");
96        }
97
98        if (!(key instanceof DHPublicKey))
99        {
100            throw new InvalidKeyException("DHKeyAgreement doPhase requires DHPublicKey");
101        }
102        DHPublicKey pubKey = (DHPublicKey)key;
103
104        if (!pubKey.getParams().getG().equals(g) || !pubKey.getParams().getP().equals(p))
105        {
106            throw new InvalidKeyException("DHPublicKey not for this KeyAgreement!");
107        }
108
109        BigInteger peerY = ((DHPublicKey)key).getY();
110        if (peerY == null || peerY.compareTo(TWO) < 0
111            || peerY.compareTo(p.subtract(ONE)) >= 0)
112        {
113            throw new InvalidKeyException("Invalid DH PublicKey");
114        }
115
116        result = peerY.modPow(x, p);
117        if (result.compareTo(ONE) == 0)
118        {
119            throw new InvalidKeyException("Shared key can't be 1");
120        }
121
122        if (lastPhase)
123        {
124            return null;
125        }
126
127        return new BCDHPublicKey(result, pubKey.getParams());
128    }
129
130    protected byte[] engineGenerateSecret()
131        throws IllegalStateException
132    {
133        if (x == null)
134        {
135            throw new IllegalStateException("Diffie-Hellman not initialised.");
136        }
137
138        return super.engineGenerateSecret();
139    }
140
141    protected int engineGenerateSecret(
142        byte[]  sharedSecret,
143        int     offset)
144        throws IllegalStateException, ShortBufferException
145    {
146        if (x == null)
147        {
148            throw new IllegalStateException("Diffie-Hellman not initialised.");
149        }
150
151        return super.engineGenerateSecret(sharedSecret, offset);
152    }
153
154    protected SecretKey engineGenerateSecret(
155        String algorithm)
156        throws NoSuchAlgorithmException
157    {
158        if (x == null)
159        {
160            throw new IllegalStateException("Diffie-Hellman not initialised.");
161        }
162
163        byte[] res = bigIntToBytes(result);
164
165        // for JSSE compatibility
166        if (algorithm.equals("TlsPremasterSecret"))
167        {
168            return new SecretKeySpec(trimZeroes(res), algorithm);
169        }
170
171        return super.engineGenerateSecret(algorithm);
172    }
173
174    protected void engineInit(
175        Key                     key,
176        AlgorithmParameterSpec  params,
177        SecureRandom            random)
178        throws InvalidKeyException, InvalidAlgorithmParameterException
179    {
180        if (!(key instanceof DHPrivateKey))
181        {
182            throw new InvalidKeyException("DHKeyAgreement requires DHPrivateKey for initialisation");
183        }
184        DHPrivateKey    privKey = (DHPrivateKey)key;
185
186        if (params != null)
187        {
188            if (params instanceof DHParameterSpec)    // p, g override.
189            {
190                DHParameterSpec p = (DHParameterSpec)params;
191
192                this.p = p.getP();
193                this.g = p.getG();
194            }
195            else if (params instanceof UserKeyingMaterialSpec)
196            {
197                this.p = privKey.getParams().getP();
198                this.g = privKey.getParams().getG();
199                this.ukmParameters = ((UserKeyingMaterialSpec)params).getUserKeyingMaterial();
200            }
201            else
202            {
203                throw new InvalidAlgorithmParameterException("DHKeyAgreement only accepts DHParameterSpec");
204            }
205        }
206        else
207        {
208            this.p = privKey.getParams().getP();
209            this.g = privKey.getParams().getG();
210        }
211
212        this.x = this.result = privKey.getX();
213    }
214
215    protected void engineInit(
216        Key             key,
217        SecureRandom    random)
218        throws InvalidKeyException
219    {
220        if (!(key instanceof DHPrivateKey))
221        {
222            throw new InvalidKeyException("DHKeyAgreement requires DHPrivateKey");
223        }
224
225        DHPrivateKey    privKey = (DHPrivateKey)key;
226
227        this.p = privKey.getParams().getP();
228        this.g = privKey.getParams().getG();
229        this.x = this.result = privKey.getX();
230    }
231
232    protected byte[] calcSecret()
233    {
234        return bigIntToBytes(result);
235    }
236
237    // BEGIN android-removed
238    // public static class DHwithRFC2631KDF
239    //    extends KeyAgreementSpi
240    // {
241    //    public DHwithRFC2631KDF()
242    //     {
243    //         super("DHwithRFC2631KDF", new DHKEKGenerator(DigestFactory.createSHA1()));
244    //     }
245    // }
246    // END android-removed
247}
248