1/*
2 * Copyright (C) 2008 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.nio.ByteBuffer;
20import java.security.AlgorithmParameters;
21import java.security.InvalidAlgorithmParameterException;
22import java.security.InvalidKeyException;
23import java.security.InvalidParameterException;
24import java.security.NoSuchAlgorithmException;
25import java.security.PrivateKey;
26import java.security.ProviderException;
27import java.security.PublicKey;
28import java.security.SignatureException;
29import java.security.SignatureSpi;
30import java.security.spec.AlgorithmParameterSpec;
31import java.security.spec.InvalidParameterSpecException;
32import java.security.spec.MGF1ParameterSpec;
33import java.security.spec.PSSParameterSpec;
34
35/**
36 * Implements the subset of the JDK Signature interface needed for
37 * signature verification using OpenSSL.
38 *
39 * @hide
40 */
41@Internal
42public class OpenSSLSignature extends SignatureSpi {
43    private static enum EngineType {
44        RSA, EC,
45    }
46
47    private NativeRef.EVP_MD_CTX ctx;
48
49    /**
50     * The current OpenSSL key we're operating on.
51     */
52    private OpenSSLKey key;
53
54    /**
55     * Holds the type of the Java algorithm.
56     */
57    private final EngineType engineType;
58
59    /**
60     * Digest algorithm (reference to {@code EVP_MD}).
61     */
62    private final long evpMdRef;
63
64    /**
65     * Holds a dummy buffer for writing single bytes to the digest.
66     */
67    private final byte[] singleByte = new byte[1];
68
69    /**
70     * True when engine is initialized to signing.
71     */
72    private boolean signing;
73
74    /**
75     * Public key algorithm context (reference to {@code EVP_PKEY_CTX}).
76     */
77    private long evpPkeyCtx;
78
79    /**
80     * Creates a new OpenSSLSignature instance for the given algorithm name.
81     *
82     * @param evpMdRef digest algorithm ({@code EVP_MD} reference).
83     */
84    private OpenSSLSignature(long evpMdRef, EngineType engineType) {
85        this.engineType = engineType;
86        this.evpMdRef = evpMdRef;
87    }
88
89    private final void resetContext() throws InvalidAlgorithmParameterException {
90        NativeRef.EVP_MD_CTX ctxLocal = new NativeRef.EVP_MD_CTX(NativeCrypto.EVP_MD_CTX_create());
91        if (signing) {
92            evpPkeyCtx = NativeCrypto.EVP_DigestSignInit(ctxLocal, evpMdRef, key.getNativeRef());
93        } else {
94            evpPkeyCtx = NativeCrypto.EVP_DigestVerifyInit(ctxLocal, evpMdRef, key.getNativeRef());
95        }
96        configureEVP_PKEY_CTX(evpPkeyCtx);
97        this.ctx = ctxLocal;
98    }
99
100    /**
101     * Configures the public key algorithm context ({@code EVP_PKEY_CTX}) associated with this
102     * operation.
103     *
104     * <p>The default implementation does nothing.
105     *
106     * @param ctx reference to the context ({@code EVP_PKEY_CTX}).
107     */
108    protected void configureEVP_PKEY_CTX(long ctx) throws InvalidAlgorithmParameterException {}
109
110    @Override
111    protected void engineUpdate(byte input) {
112        singleByte[0] = input;
113        engineUpdate(singleByte, 0, 1);
114    }
115
116    @Override
117    protected void engineUpdate(byte[] input, int offset, int len) {
118        final NativeRef.EVP_MD_CTX ctxLocal = ctx;
119        if (signing) {
120            NativeCrypto.EVP_DigestSignUpdate(ctxLocal, input, offset, len);
121        } else {
122            NativeCrypto.EVP_DigestVerifyUpdate(ctxLocal, input, offset, len);
123        }
124    }
125
126    @Override
127    protected void engineUpdate(ByteBuffer input) {
128        // Optimization: Avoid copying/allocation for direct buffers because their contents are
129        // stored as a contiguous region in memory and thus can be efficiently accessed from native
130        // code.
131
132        if (!input.hasRemaining()) {
133            return;
134        }
135
136        if (!input.isDirect()) {
137            super.engineUpdate(input);
138            return;
139        }
140
141        long baseAddress = NativeCrypto.getDirectBufferAddress(input);
142        if (baseAddress == 0) {
143            // Direct buffer's contents can't be accessed from JNI  -- superclass's implementation
144            // is good enough to handle this.
145            super.engineUpdate(input);
146            return;
147        }
148
149        // Process the contents between Buffer's position and limit (remaining() number of bytes)
150        int position = input.position();
151        if (position < 0) {
152            throw new RuntimeException("Negative position");
153        }
154        long ptr = baseAddress + position;
155        int len = input.remaining();
156        if (len < 0) {
157            throw new RuntimeException("Negative remaining amount");
158        }
159
160        final NativeRef.EVP_MD_CTX ctxLocal = ctx;
161        if (signing) {
162            NativeCrypto.EVP_DigestSignUpdateDirect(ctxLocal, ptr, len);
163        } else {
164            NativeCrypto.EVP_DigestVerifyUpdateDirect(ctxLocal, ptr, len);
165        }
166        input.position(position + len);
167    }
168
169    @Deprecated
170    @Override
171    protected Object engineGetParameter(String param) throws InvalidParameterException {
172        return null;
173    }
174
175    private void checkEngineType(OpenSSLKey pkey) throws InvalidKeyException {
176        final int pkeyType = NativeCrypto.EVP_PKEY_type(pkey.getNativeRef());
177
178        switch (engineType) {
179            case RSA:
180                if (pkeyType != NativeConstants.EVP_PKEY_RSA) {
181                    throw new InvalidKeyException("Signature initialized as " + engineType
182                            + " (not RSA)");
183                }
184                break;
185            case EC:
186                if (pkeyType != NativeConstants.EVP_PKEY_EC) {
187                    throw new InvalidKeyException("Signature initialized as " + engineType
188                            + " (not EC)");
189                }
190                break;
191            default:
192                throw new InvalidKeyException("Key must be of type " + engineType);
193        }
194    }
195
196    private void initInternal(OpenSSLKey newKey, boolean signing) throws InvalidKeyException {
197        checkEngineType(newKey);
198        key = newKey;
199
200        this.signing = signing;
201        try {
202            resetContext();
203        } catch (InvalidAlgorithmParameterException e) {
204            throw new InvalidKeyException(e);
205        }
206    }
207
208    @Override
209    protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
210        initInternal(OpenSSLKey.fromPrivateKey(privateKey), true);
211    }
212
213    @Override
214    protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
215        initInternal(OpenSSLKey.fromPublicKey(publicKey), false);
216    }
217
218    @Deprecated
219    @Override
220    protected void engineSetParameter(String param, Object value) throws InvalidParameterException {
221    }
222
223    @Override
224    @SuppressWarnings("Finally")
225    protected byte[] engineSign() throws SignatureException {
226        final NativeRef.EVP_MD_CTX ctxLocal = ctx;
227        try {
228            return NativeCrypto.EVP_DigestSignFinal(ctxLocal);
229        } catch (Exception ex) {
230            throw new SignatureException(ex);
231        } finally {
232            /*
233             * Java expects the digest context to be reset completely after sign
234             * calls.
235             */
236            try {
237                resetContext();
238            } catch (InvalidAlgorithmParameterException e) {
239                throw new AssertionError("Reset of context failed after it was successful once");
240            }
241        }
242    }
243
244    @Override
245    @SuppressWarnings("Finally")
246    protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
247        final NativeRef.EVP_MD_CTX ctxLocal = ctx;
248        try {
249            return NativeCrypto.EVP_DigestVerifyFinal(ctxLocal, sigBytes, 0, sigBytes.length);
250        } catch (Exception ex) {
251            throw new SignatureException(ex);
252        } finally {
253            /*
254             * Java expects the digest context to be reset completely after
255             * verify calls.
256             */
257            try {
258                resetContext();
259            } catch (InvalidAlgorithmParameterException e) {
260                throw new AssertionError("Reset of context failed after it was successful once");
261            }
262        }
263    }
264
265    /**
266     * Returns the public key algorithm context ({@code EVP_PKEY_CTX} reference) associated with
267     * this operation or {@code 0} if operation hasn't been initialized.
268     */
269    protected final long getEVP_PKEY_CTX() {
270        return evpPkeyCtx;
271    }
272
273    /**
274     * Base class for {@code RSASSA-PKCS1-v1_5} signatures.
275     */
276    abstract static class RSAPKCS1Padding extends OpenSSLSignature {
277        RSAPKCS1Padding(long evpMdRef) {
278            super(evpMdRef, EngineType.RSA);
279        }
280
281        @Override
282        protected final void configureEVP_PKEY_CTX(long ctx)
283                throws InvalidAlgorithmParameterException {
284            NativeCrypto.EVP_PKEY_CTX_set_rsa_padding(ctx, NativeConstants.RSA_PKCS1_PADDING);
285        }
286    }
287
288    public static final class MD5RSA extends RSAPKCS1Padding {
289        public MD5RSA() {
290            super(EvpMdRef.MD5.EVP_MD);
291        }
292    }
293    public static final class SHA1RSA extends RSAPKCS1Padding {
294        public SHA1RSA() {
295            super(EvpMdRef.SHA1.EVP_MD);
296        }
297    }
298    public static final class SHA224RSA extends RSAPKCS1Padding {
299        public SHA224RSA() {
300            super(EvpMdRef.SHA224.EVP_MD);
301        }
302    }
303    public static final class SHA256RSA extends RSAPKCS1Padding {
304        public SHA256RSA() {
305            super(EvpMdRef.SHA256.EVP_MD);
306        }
307    }
308    public static final class SHA384RSA extends RSAPKCS1Padding {
309        public SHA384RSA() {
310            super(EvpMdRef.SHA384.EVP_MD);
311        }
312    }
313    public static final class SHA512RSA extends RSAPKCS1Padding {
314        public SHA512RSA() {
315            super(EvpMdRef.SHA512.EVP_MD);
316        }
317    }
318
319    public static final class SHA1ECDSA extends OpenSSLSignature {
320        public SHA1ECDSA() {
321            super(EvpMdRef.SHA1.EVP_MD, EngineType.EC);
322        }
323    }
324    public static final class SHA224ECDSA extends OpenSSLSignature {
325        public SHA224ECDSA() {
326            super(EvpMdRef.SHA224.EVP_MD, EngineType.EC);
327        }
328    }
329    public static final class SHA256ECDSA extends OpenSSLSignature {
330        public SHA256ECDSA() {
331            super(EvpMdRef.SHA256.EVP_MD, EngineType.EC);
332        }
333    }
334    public static final class SHA384ECDSA extends OpenSSLSignature {
335        public SHA384ECDSA() {
336            super(EvpMdRef.SHA384.EVP_MD, EngineType.EC);
337        }
338    }
339    public static final class SHA512ECDSA extends OpenSSLSignature {
340        public SHA512ECDSA() {
341            super(EvpMdRef.SHA512.EVP_MD, EngineType.EC);
342        }
343    }
344
345    /**
346     * Base class for {@code RSASSA-PSS} signatures.
347     */
348    abstract static class RSAPSSPadding extends OpenSSLSignature {
349        private static final int TRAILER_FIELD_BC_ID = 1;
350
351        private final String contentDigestAlgorithm;
352
353        private String mgf1DigestAlgorithm;
354        private long mgf1EvpMdRef;
355        private int saltSizeBytes;
356
357        public RSAPSSPadding(
358                long contentDigestEvpMdRef, String contentDigestAlgorithm, int saltSizeBytes) {
359            super(contentDigestEvpMdRef, EngineType.RSA);
360            this.contentDigestAlgorithm = contentDigestAlgorithm;
361            this.mgf1DigestAlgorithm = contentDigestAlgorithm;
362            this.mgf1EvpMdRef = contentDigestEvpMdRef;
363            this.saltSizeBytes = saltSizeBytes;
364        }
365
366        @Override
367        protected final void configureEVP_PKEY_CTX(long ctx)
368                throws InvalidAlgorithmParameterException {
369            NativeCrypto.EVP_PKEY_CTX_set_rsa_padding(ctx, NativeConstants.RSA_PKCS1_PSS_PADDING);
370            NativeCrypto.EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, mgf1EvpMdRef);
371            NativeCrypto.EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, saltSizeBytes);
372        }
373
374        @Override
375        protected final void engineSetParameter(AlgorithmParameterSpec params)
376                throws InvalidAlgorithmParameterException {
377            if (!(params instanceof PSSParameterSpec)) {
378                throw new InvalidAlgorithmParameterException(
379                        "Unsupported parameter: " + params + ". Only "
380                                + PSSParameterSpec.class.getName() + " supported");
381            }
382            PSSParameterSpec spec = (PSSParameterSpec) params;
383            String specContentDigest = EvpMdRef
384                    .getJcaDigestAlgorithmStandardName(spec.getDigestAlgorithm());
385            if (specContentDigest == null) {
386                throw new InvalidAlgorithmParameterException(
387                        "Unsupported content digest algorithm: " + spec.getDigestAlgorithm());
388            } else if (!contentDigestAlgorithm.equalsIgnoreCase(specContentDigest)) {
389                throw new InvalidAlgorithmParameterException(
390                        "Changing content digest algorithm not supported");
391            }
392
393            String specMgfAlgorithm = spec.getMGFAlgorithm();
394            if ((!EvpMdRef.MGF1_ALGORITHM_NAME.equalsIgnoreCase(specMgfAlgorithm))
395                    && (!EvpMdRef.MGF1_OID.equals(specMgfAlgorithm))) {
396                throw new InvalidAlgorithmParameterException(
397                        "Unsupported MGF algorithm: " + specMgfAlgorithm + ". Only "
398                                + EvpMdRef.MGF1_ALGORITHM_NAME + " supported");
399            }
400
401            AlgorithmParameterSpec mgfSpec = spec.getMGFParameters();
402            if (!(mgfSpec instanceof MGF1ParameterSpec)) {
403                throw new InvalidAlgorithmParameterException(
404                        "Unsupported MGF parameters: " + mgfSpec + ". Only "
405                                + MGF1ParameterSpec.class.getName() + " supported");
406            }
407            MGF1ParameterSpec specMgf1Spec = (MGF1ParameterSpec) spec.getMGFParameters();
408
409            String specMgf1Digest = EvpMdRef
410                    .getJcaDigestAlgorithmStandardName(specMgf1Spec.getDigestAlgorithm());
411            if (specMgf1Digest == null) {
412                throw new InvalidAlgorithmParameterException(
413                        "Unsupported MGF1 digest algorithm: " + specMgf1Spec.getDigestAlgorithm());
414            }
415            long specMgf1EvpMdRef;
416            try {
417                specMgf1EvpMdRef = EvpMdRef
418                        .getEVP_MDByJcaDigestAlgorithmStandardName(specMgf1Digest);
419            } catch (NoSuchAlgorithmException e) {
420                throw new ProviderException("Failed to obtain EVP_MD for " + specMgf1Digest, e);
421            }
422
423            int specSaltSizeBytes = spec.getSaltLength();
424            if (specSaltSizeBytes < 0) {
425                throw new InvalidAlgorithmParameterException(
426                        "Salt length must be non-negative: " + specSaltSizeBytes);
427            }
428
429            int specTrailer = spec.getTrailerField();
430            if (specTrailer != TRAILER_FIELD_BC_ID) {
431                throw new InvalidAlgorithmParameterException(
432                        "Unsupported trailer field: " + specTrailer + ". Only "
433                                + TRAILER_FIELD_BC_ID + " supported");
434            }
435
436            this.mgf1DigestAlgorithm = specMgf1Digest;
437            this.mgf1EvpMdRef = specMgf1EvpMdRef;
438            this.saltSizeBytes = specSaltSizeBytes;
439
440            long ctx = getEVP_PKEY_CTX();
441            if (ctx != 0) {
442                configureEVP_PKEY_CTX(ctx);
443            }
444        }
445
446        @Override
447        protected final AlgorithmParameters engineGetParameters() {
448            try {
449                AlgorithmParameters result = AlgorithmParameters.getInstance("PSS");
450                result.init(
451                        new PSSParameterSpec(
452                                contentDigestAlgorithm,
453                                EvpMdRef.MGF1_ALGORITHM_NAME,
454                                new MGF1ParameterSpec(mgf1DigestAlgorithm),
455                                saltSizeBytes,
456                                TRAILER_FIELD_BC_ID));
457                return result;
458            } catch (NoSuchAlgorithmException | InvalidParameterSpecException e) {
459                throw new ProviderException("Failed to create PSS AlgorithmParameters", e);
460            }
461        }
462    }
463
464    public static final class SHA1RSAPSS extends RSAPSSPadding {
465        public SHA1RSAPSS() {
466            super(EvpMdRef.SHA1.EVP_MD, EvpMdRef.SHA1.JCA_NAME, EvpMdRef.SHA1.SIZE_BYTES);
467        }
468    }
469
470    public static final class SHA224RSAPSS extends RSAPSSPadding {
471        public SHA224RSAPSS() {
472            super(EvpMdRef.SHA224.EVP_MD, EvpMdRef.SHA224.JCA_NAME, EvpMdRef.SHA224.SIZE_BYTES);
473        }
474    }
475
476    public static final class SHA256RSAPSS extends RSAPSSPadding {
477        public SHA256RSAPSS() {
478            super(EvpMdRef.SHA256.EVP_MD, EvpMdRef.SHA256.JCA_NAME, EvpMdRef.SHA256.SIZE_BYTES);
479        }
480    }
481
482    public static final class SHA384RSAPSS extends RSAPSSPadding {
483        public SHA384RSAPSS() {
484            super(EvpMdRef.SHA384.EVP_MD, EvpMdRef.SHA384.JCA_NAME, EvpMdRef.SHA384.SIZE_BYTES);
485        }
486    }
487
488    public static final class SHA512RSAPSS extends RSAPSSPadding {
489        public SHA512RSAPSS() {
490            super(EvpMdRef.SHA512.EVP_MD, EvpMdRef.SHA512.JCA_NAME, EvpMdRef.SHA512.SIZE_BYTES);
491        }
492    }
493}
494