1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.conscrypt;
18
19import java.io.IOException;
20import java.io.NotSerializableException;
21import java.io.ObjectInputStream;
22import java.io.ObjectOutputStream;
23import java.math.BigInteger;
24import java.security.InvalidKeyException;
25import java.security.interfaces.DSAParams;
26import java.security.interfaces.DSAPrivateKey;
27import java.security.spec.DSAPrivateKeySpec;
28import java.security.spec.InvalidKeySpecException;
29
30public class OpenSSLDSAPrivateKey implements DSAPrivateKey, OpenSSLKeyHolder {
31    private static final long serialVersionUID = 6524734576187424628L;
32
33    private transient OpenSSLKey key;
34
35    private transient OpenSSLDSAParams params;
36
37    OpenSSLDSAPrivateKey(OpenSSLKey key) {
38        this.key = key;
39    }
40
41    @Override
42    public OpenSSLKey getOpenSSLKey() {
43        return key;
44    }
45
46    OpenSSLDSAPrivateKey(DSAPrivateKeySpec dsaKeySpec) throws InvalidKeySpecException {
47        try {
48            key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_DSA(
49                    dsaKeySpec.getP().toByteArray(),
50                    dsaKeySpec.getQ().toByteArray(),
51                    dsaKeySpec.getG().toByteArray(),
52                    null,
53                    dsaKeySpec.getX().toByteArray()));
54        } catch (Exception e) {
55            throw new InvalidKeySpecException(e);
56        }
57    }
58
59    private void ensureReadParams() {
60        if (params == null) {
61            params = new OpenSSLDSAParams(key);
62        }
63    }
64
65    static OpenSSLKey getInstance(DSAPrivateKey dsaPrivateKey) throws InvalidKeyException {
66        try {
67            DSAParams dsaParams = dsaPrivateKey.getParams();
68            return new OpenSSLKey(NativeCrypto.EVP_PKEY_new_DSA(
69                    dsaParams.getP().toByteArray(),
70                    dsaParams.getQ().toByteArray(),
71                    dsaParams.getG().toByteArray(),
72                    null,
73                    dsaPrivateKey.getX().toByteArray()));
74        } catch (Exception e) {
75            throw new InvalidKeyException(e);
76        }
77    }
78
79    @Override
80    public DSAParams getParams() {
81        ensureReadParams();
82        return params;
83    }
84
85    @Override
86    public String getAlgorithm() {
87        return "DSA";
88    }
89
90    @Override
91    public String getFormat() {
92        /*
93         * If we're using an OpenSSL ENGINE, there's no guarantee we can export
94         * the key. Returning {@code null} tells the caller that there's no
95         * encoded format.
96         */
97        if (key.isEngineBased()) {
98            return null;
99        }
100
101        return "PKCS#8";
102    }
103
104    @Override
105    public byte[] getEncoded() {
106        /*
107         * If we're using an OpenSSL ENGINE, there's no guarantee we can export
108         * the key. Returning {@code null} tells the caller that there's no
109         * encoded format.
110         */
111        if (key.isEngineBased()) {
112            return null;
113        }
114
115        return NativeCrypto.i2d_PKCS8_PRIV_KEY_INFO(key.getPkeyContext());
116    }
117
118    @Override
119    public BigInteger getX() {
120        if (key.isEngineBased()) {
121            throw new UnsupportedOperationException("private key value X cannot be extracted");
122        }
123
124        ensureReadParams();
125        return params.getX();
126    }
127
128    @Override
129    public boolean equals(Object o) {
130        if (o == this) {
131            return true;
132        }
133
134        if (o instanceof OpenSSLDSAPrivateKey) {
135            OpenSSLDSAPrivateKey other = (OpenSSLDSAPrivateKey) o;
136
137            /*
138             * We can shortcut the true case, but it still may be equivalent but
139             * different copies.
140             */
141            if (key.equals(other.getOpenSSLKey())) {
142                return true;
143            }
144        }
145
146        if (!(o instanceof DSAPrivateKey)) {
147            return false;
148        }
149
150        ensureReadParams();
151
152        final BigInteger x = params.getX();
153        if (x == null) {
154            /*
155             * If our X is null, we can't tell if these two private keys are
156             * equivalent. This usually happens if this key is ENGINE-based. If
157             * the other key was ENGINE-based, we should have caught it in the
158             * OpenSSLDSAPrivateKey case.
159             */
160            return false;
161        }
162
163        final DSAPrivateKey other = (DSAPrivateKey) o;
164        return x.equals(other.getX()) && params.equals(other.getParams());
165    }
166
167    @Override
168    public int hashCode() {
169        ensureReadParams();
170
171        int hash = 1;
172
173        final BigInteger x = getX();
174        if (x != null) {
175            hash = hash * 3 + x.hashCode();
176        }
177
178        hash = hash * 7 + params.hashCode();
179
180        return hash;
181    }
182
183    @Override
184    public String toString() {
185        final StringBuilder sb = new StringBuilder("OpenSSLDSAPrivateKey{");
186
187        if (key.isEngineBased()) {
188            sb.append("key=");
189            sb.append(key);
190            sb.append('}');
191            return sb.toString();
192        }
193
194        ensureReadParams();
195        sb.append("X=");
196        sb.append(params.getX().toString(16));
197        sb.append(',');
198        sb.append("params=");
199        sb.append(params.toString());
200        sb.append('}');
201
202        return sb.toString();
203    }
204
205    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
206        stream.defaultReadObject();
207
208        final BigInteger g = (BigInteger) stream.readObject();
209        final BigInteger p = (BigInteger) stream.readObject();
210        final BigInteger q = (BigInteger) stream.readObject();
211        final BigInteger x = (BigInteger) stream.readObject();
212
213        key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_DSA(
214                p.toByteArray(),
215                q.toByteArray(),
216                g.toByteArray(),
217                null,
218                x.toByteArray()));
219    }
220
221    private void writeObject(ObjectOutputStream stream) throws IOException {
222        if (getOpenSSLKey().isEngineBased()) {
223            throw new NotSerializableException("engine-based keys can not be serialized");
224        }
225
226        stream.defaultWriteObject();
227
228        ensureReadParams();
229        stream.writeObject(params.getG());
230        stream.writeObject(params.getP());
231        stream.writeObject(params.getQ());
232        stream.writeObject(params.getX());
233    }
234}
235