| // Copyright 2018 Google Inc. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| package com.google.crypto.tink.subtle; |
| |
| import com.google.crypto.tink.PublicKeyVerify; |
| import com.google.crypto.tink.subtle.Enums.HashType; |
| import java.math.BigInteger; |
| import java.security.GeneralSecurityException; |
| import java.security.MessageDigest; |
| import java.security.interfaces.RSAPublicKey; |
| |
| /** |
| * RsaSsaPkcs1 (i.e. RSA Signature Schemes with Appendix (SSA) using PKCS1-v1_5 encoding) verifying |
| * with JCE. |
| */ |
| public final class RsaSsaPkcs1VerifyJce implements PublicKeyVerify { |
| private static final String ASN_PREFIX_SHA256 = "3031300d060960864801650304020105000420"; |
| private static final String ASN_PREFIX_SHA512 = "3051300d060960864801650304020305000440"; |
| |
| private final RSAPublicKey publicKey; |
| private final HashType hash; |
| |
| public RsaSsaPkcs1VerifyJce(final RSAPublicKey pubKey, HashType hash) |
| throws GeneralSecurityException { |
| Validators.validateSignatureHash(hash); |
| Validators.validateRsaModulusSize(pubKey.getModulus().bitLength()); |
| this.publicKey = pubKey; |
| this.hash = hash; |
| } |
| |
| @Override |
| public void verify(final byte[] signature, final byte[] data) throws GeneralSecurityException { |
| // The algorithm is described at (https://tools.ietf.org/html/rfc8017#section-8.2). As signature |
| // verification is a public operation, throwing different exception messages doesn't give |
| // attacker any useful information. |
| BigInteger e = publicKey.getPublicExponent(); |
| BigInteger n = publicKey.getModulus(); |
| int nLengthInBytes = (n.bitLength() + 7) / 8; |
| |
| // Step 1. Length checking. |
| if (nLengthInBytes != signature.length) { |
| throw new GeneralSecurityException("invalid signature's length"); |
| } |
| |
| // Step 2. RSA verification. |
| BigInteger s = SubtleUtil.bytes2Integer(signature); |
| if (s.compareTo(n) >= 0) { |
| throw new GeneralSecurityException("signature out of range"); |
| } |
| BigInteger m = s.modPow(e, n); |
| byte[] em = SubtleUtil.integer2Bytes(m, nLengthInBytes); |
| |
| // Step 3. PKCS1 encoding. |
| byte[] expectedEm = emsaPkcs1(data, nLengthInBytes, hash); |
| |
| // Step 4. Compare the results. |
| if (!Bytes.equal(em, expectedEm)) { |
| throw new GeneralSecurityException("invalid signature"); |
| } |
| } |
| |
| // https://tools.ietf.org/html/rfc8017#section-9.2. |
| private byte[] emsaPkcs1(byte[] m, int emLen, HashType hash) throws GeneralSecurityException { |
| Validators.validateSignatureHash(hash); |
| MessageDigest digest = |
| EngineFactory.MESSAGE_DIGEST.getInstance(SubtleUtil.toDigestAlgo(this.hash)); |
| digest.update(m); |
| byte[] h = digest.digest(); |
| byte[] asnPrefix = toAsnPrefix(hash); |
| int tLen = asnPrefix.length + h.length; |
| if (emLen < tLen + 11) { |
| throw new GeneralSecurityException("intended encoded message length too short"); |
| } |
| byte[] em = new byte[emLen]; |
| int offset = 0; |
| em[offset++] = 0x00; |
| em[offset++] = 0x01; |
| for (int i = 0; i < emLen - tLen - 3; i++) { |
| em[offset++] = (byte) 0xff; |
| } |
| em[offset++] = 0x00; |
| System.arraycopy(asnPrefix, 0, em, offset, asnPrefix.length); |
| System.arraycopy(h, 0, em, offset + asnPrefix.length, h.length); |
| return em; |
| } |
| |
| private byte[] toAsnPrefix(HashType hash) throws GeneralSecurityException { |
| switch (hash) { |
| case SHA256: |
| return Hex.decode(ASN_PREFIX_SHA256); |
| case SHA512: |
| return Hex.decode(ASN_PREFIX_SHA512); |
| default: |
| throw new GeneralSecurityException("Unsupported hash " + hash); |
| } |
| } |
| } |