• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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