Home | History | Annotate | Download | only in conscrypt
      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 
     17 package org.conscrypt;
     18 
     19 import java.nio.ByteBuffer;
     20 import java.security.AlgorithmParameters;
     21 import java.security.InvalidAlgorithmParameterException;
     22 import java.security.InvalidKeyException;
     23 import java.security.InvalidParameterException;
     24 import java.security.NoSuchAlgorithmException;
     25 import java.security.PrivateKey;
     26 import java.security.ProviderException;
     27 import java.security.PublicKey;
     28 import java.security.SignatureException;
     29 import java.security.SignatureSpi;
     30 import java.security.spec.AlgorithmParameterSpec;
     31 import java.security.spec.InvalidParameterSpecException;
     32 import java.security.spec.MGF1ParameterSpec;
     33 import 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
     42 public class OpenSSLSignature extends SignatureSpi {
     43     private 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 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         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 e) {
    459                 throw new ProviderException("Failed to create PSS AlgorithmParameters", e);
    460             } catch (InvalidParameterSpecException e) {
    461                 throw new ProviderException("Failed to create PSS AlgorithmParameters", e);
    462             }
    463         }
    464     }
    465 
    466     public static final class SHA1RSAPSS extends RSAPSSPadding {
    467         public SHA1RSAPSS() {
    468             super(EvpMdRef.SHA1.EVP_MD, EvpMdRef.SHA1.JCA_NAME, EvpMdRef.SHA1.SIZE_BYTES);
    469         }
    470     }
    471 
    472     public static final class SHA224RSAPSS extends RSAPSSPadding {
    473         public SHA224RSAPSS() {
    474             super(EvpMdRef.SHA224.EVP_MD, EvpMdRef.SHA224.JCA_NAME, EvpMdRef.SHA224.SIZE_BYTES);
    475         }
    476     }
    477 
    478     public static final class SHA256RSAPSS extends RSAPSSPadding {
    479         public SHA256RSAPSS() {
    480             super(EvpMdRef.SHA256.EVP_MD, EvpMdRef.SHA256.JCA_NAME, EvpMdRef.SHA256.SIZE_BYTES);
    481         }
    482     }
    483 
    484     public static final class SHA384RSAPSS extends RSAPSSPadding {
    485         public SHA384RSAPSS() {
    486             super(EvpMdRef.SHA384.EVP_MD, EvpMdRef.SHA384.JCA_NAME, EvpMdRef.SHA384.SIZE_BYTES);
    487         }
    488     }
    489 
    490     public static final class SHA512RSAPSS extends RSAPSSPadding {
    491         public SHA512RSAPSS() {
    492             super(EvpMdRef.SHA512.EVP_MD, EvpMdRef.SHA512.JCA_NAME, EvpMdRef.SHA512.SIZE_BYTES);
    493         }
    494     }
    495 }
    496