1 // Copyright 2017 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 //////////////////////////////////////////////////////////////////////////////// 16 17 package com.google.crypto.tink.signature; 18 19 import com.google.crypto.tink.KeysetReader; 20 import com.google.crypto.tink.PemKeyType; 21 import com.google.crypto.tink.proto.EcdsaParams; 22 import com.google.crypto.tink.proto.EcdsaPublicKey; 23 import com.google.crypto.tink.proto.EcdsaSignatureEncoding; 24 import com.google.crypto.tink.proto.EllipticCurveType; 25 import com.google.crypto.tink.proto.EncryptedKeyset; 26 import com.google.crypto.tink.proto.HashType; 27 import com.google.crypto.tink.proto.KeyData; 28 import com.google.crypto.tink.proto.KeyStatusType; 29 import com.google.crypto.tink.proto.Keyset; 30 import com.google.crypto.tink.proto.OutputPrefixType; 31 import com.google.crypto.tink.proto.RsaSsaPkcs1Params; 32 import com.google.crypto.tink.proto.RsaSsaPkcs1PublicKey; 33 import com.google.crypto.tink.proto.RsaSsaPssParams; 34 import com.google.crypto.tink.proto.RsaSsaPssPublicKey; 35 import com.google.crypto.tink.signature.internal.SigUtil; 36 import com.google.crypto.tink.subtle.Random; 37 import com.google.errorprone.annotations.CanIgnoreReturnValue; 38 import java.io.BufferedReader; 39 import java.io.IOException; 40 import java.io.StringReader; 41 import java.security.Key; 42 import java.security.interfaces.ECPublicKey; 43 import java.security.interfaces.RSAPublicKey; 44 import java.util.ArrayList; 45 import java.util.List; 46 import javax.annotation.Nullable; 47 48 /** 49 * SignaturePemKeysetReader is a {@link KeysetReader} that can read digital signature keys in PEM 50 * format (RFC 7468). 51 * 52 * <p>Only supports public keys. 53 * 54 * <p>Private, unknown or invalid keys are ignored. 55 * 56 * <h3>Usage</h3> 57 * 58 * <pre>{@code 59 * import com.google.crypto.tink.PemKeyType; 60 * 61 * String pem = ...; 62 * PemKeyType type = ...; 63 * KeysetReader reader = SignaturePemKeysetReader.newBuilder().addPem(pem, type).build(); 64 * }</pre> 65 */ 66 public final class SignaturePemKeysetReader implements KeysetReader { 67 private List<PemKey> pemKeys; 68 SignaturePemKeysetReader(List<PemKey> pemKeys)69 SignaturePemKeysetReader(List<PemKey> pemKeys) { 70 this.pemKeys = pemKeys; 71 } 72 73 /** Returns a {@link Builder} for {@link SignaturePemKeysetReader}. */ newBuilder()74 public static Builder newBuilder() { 75 return new Builder(); 76 } 77 78 /** Builder for SignaturePemKeysetReader */ 79 public static final class Builder { 80 private List<PemKey> pemKeys = new ArrayList<PemKey>(); 81 Builder()82 Builder() {} 83 build()84 public KeysetReader build() { 85 return new SignaturePemKeysetReader(pemKeys); 86 } 87 88 /** 89 * Adds a PEM. 90 * 91 * <p>A single PEM can contain multiple keys, but all must have the same {@code keyType}. 92 * Invalid or unparsable keys are ignored. 93 * 94 * <p>The first key in the first added PEM is the primary key. 95 */ 96 @CanIgnoreReturnValue addPem(String pem, PemKeyType keyType)97 public Builder addPem(String pem, PemKeyType keyType) { 98 PemKey pemKey = new PemKey(); 99 pemKey.reader = new BufferedReader(new StringReader(pem)); 100 pemKey.type = keyType; 101 pemKeys.add(pemKey); 102 return this; 103 } 104 } 105 106 private static final class PemKey { 107 BufferedReader reader; 108 PemKeyType type; 109 } 110 111 @Override read()112 public Keyset read() throws IOException { 113 Keyset.Builder keyset = Keyset.newBuilder(); 114 for (PemKey pemKey : pemKeys) { 115 for (Keyset.Key key = readKey(pemKey.reader, pemKey.type); 116 key != null; 117 key = readKey(pemKey.reader, pemKey.type)) { 118 keyset.addKey(key); 119 } 120 } 121 122 if (keyset.getKeyCount() == 0) { 123 throw new IOException("cannot find any key"); 124 } 125 // Use the first key as the primary key id. 126 keyset.setPrimaryKeyId(keyset.getKey(0).getKeyId()); 127 return keyset.build(); 128 } 129 130 @Override readEncrypted()131 public EncryptedKeyset readEncrypted() throws IOException { 132 throw new UnsupportedOperationException(); 133 } 134 135 /** Reads a single PEM key from {@code reader}. Invalid or unparsable PEM would be ignored */ 136 @Nullable readKey(BufferedReader reader, PemKeyType pemKeyType)137 private static Keyset.Key readKey(BufferedReader reader, PemKeyType pemKeyType) 138 throws IOException { 139 Key key = pemKeyType.readKey(reader); 140 if (key == null) { 141 return null; 142 } 143 144 KeyData keyData; 145 if (key instanceof RSAPublicKey) { 146 keyData = convertRsaPublicKey(pemKeyType, (RSAPublicKey) key); 147 } else if (key instanceof ECPublicKey) { 148 keyData = convertEcPublicKey(pemKeyType, (ECPublicKey) key); 149 } else { 150 // Private keys are ignored. 151 return null; 152 } 153 154 return Keyset.Key.newBuilder() 155 .setKeyData(keyData) 156 .setStatus(KeyStatusType.ENABLED) 157 .setOutputPrefixType(OutputPrefixType.RAW) // PEM keys don't add any prefix to signatures 158 .setKeyId(Random.randInt()) 159 .build(); 160 } 161 convertRsaPublicKey(PemKeyType pemKeyType, RSAPublicKey key)162 private static KeyData convertRsaPublicKey(PemKeyType pemKeyType, RSAPublicKey key) 163 throws IOException { 164 if (pemKeyType.algorithm.equals("RSASSA-PKCS1-v1_5")) { 165 RsaSsaPkcs1Params params = 166 RsaSsaPkcs1Params.newBuilder().setHashType(getHashType(pemKeyType)).build(); 167 RsaSsaPkcs1PublicKey pkcs1PubKey = 168 RsaSsaPkcs1PublicKey.newBuilder() 169 .setVersion(0) 170 .setParams(params) 171 .setE(SigUtil.toUnsignedIntByteString(key.getPublicExponent())) 172 .setN(SigUtil.toUnsignedIntByteString(key.getModulus())) 173 .build(); 174 return KeyData.newBuilder() 175 .setTypeUrl(RsaSsaPkcs1VerifyKeyManager.getKeyType()) 176 .setValue(pkcs1PubKey.toByteString()) 177 .setKeyMaterialType(KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC) 178 .build(); 179 } else if (pemKeyType.algorithm.equals("RSASSA-PSS")) { 180 RsaSsaPssParams params = 181 RsaSsaPssParams.newBuilder() 182 .setSigHash(getHashType(pemKeyType)) 183 .setMgf1Hash(getHashType(pemKeyType)) 184 .setSaltLength(getDigestSizeInBytes(pemKeyType)) 185 .build(); 186 RsaSsaPssPublicKey pssPubKey = 187 RsaSsaPssPublicKey.newBuilder() 188 .setVersion(0) 189 .setParams(params) 190 .setE(SigUtil.toUnsignedIntByteString(key.getPublicExponent())) 191 .setN(SigUtil.toUnsignedIntByteString(key.getModulus())) 192 .build(); 193 return KeyData.newBuilder() 194 .setTypeUrl(RsaSsaPssVerifyKeyManager.getKeyType()) 195 .setValue(pssPubKey.toByteString()) 196 .setKeyMaterialType(KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC) 197 .build(); 198 } 199 throw new IOException("unsupported RSA signature algorithm: " + pemKeyType.algorithm); 200 } 201 convertEcPublicKey(PemKeyType pemKeyType, ECPublicKey key)202 private static KeyData convertEcPublicKey(PemKeyType pemKeyType, ECPublicKey key) 203 throws IOException { 204 if (pemKeyType.algorithm.equals("ECDSA")) { 205 EcdsaParams params = 206 EcdsaParams.newBuilder() 207 .setHashType(getHashType(pemKeyType)) 208 .setCurve(getCurveType(pemKeyType)) 209 .setEncoding(EcdsaSignatureEncoding.DER) 210 .build(); 211 EcdsaPublicKey ecdsaPubKey = 212 EcdsaPublicKey.newBuilder() 213 .setVersion(0) 214 .setParams(params) 215 .setX(SigUtil.toUnsignedIntByteString(key.getW().getAffineX())) 216 .setY(SigUtil.toUnsignedIntByteString(key.getW().getAffineY())) 217 .build(); 218 219 return KeyData.newBuilder() 220 .setTypeUrl(EcdsaVerifyKeyManager.getKeyType()) 221 .setValue(ecdsaPubKey.toByteString()) 222 .setKeyMaterialType(KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC) 223 .build(); 224 } 225 throw new IOException("unsupported EC signature algorithm: " + pemKeyType.algorithm); 226 } 227 getHashType(PemKeyType pemKeyType)228 private static HashType getHashType(PemKeyType pemKeyType) { 229 switch (pemKeyType.hash) { 230 case SHA256: 231 return HashType.SHA256; 232 case SHA384: 233 return HashType.SHA384; 234 case SHA512: 235 return HashType.SHA512; 236 default: 237 break; 238 } 239 throw new IllegalArgumentException("unsupported hash type: " + pemKeyType.hash.name()); 240 } 241 getDigestSizeInBytes(PemKeyType pemKeyType)242 private static int getDigestSizeInBytes(PemKeyType pemKeyType) { 243 switch (pemKeyType.hash) { 244 case SHA256: 245 return 32; 246 case SHA384: 247 return 48; 248 case SHA512: 249 return 64; 250 default: 251 break; 252 } 253 throw new IllegalArgumentException("unsupported hash type: " + pemKeyType.hash.name()); 254 } 255 getCurveType(PemKeyType pemKeyType)256 private static EllipticCurveType getCurveType(PemKeyType pemKeyType) { 257 switch (pemKeyType.keySizeInBits) { 258 case 256: 259 return EllipticCurveType.NIST_P256; 260 case 384: 261 return EllipticCurveType.NIST_P384; 262 case 521: 263 return EllipticCurveType.NIST_P521; 264 default: 265 break; 266 } 267 throw new IllegalArgumentException( 268 "unsupported curve for key size: " + pemKeyType.keySizeInBits); 269 } 270 } 271