• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 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.testing;
18 
19 import com.google.crypto.tink.InsecureSecretKeyAccess;
20 import com.google.crypto.tink.KeysetHandle;
21 import com.google.crypto.tink.TinkProtoKeysetFormat;
22 import com.google.crypto.tink.jwt.JwkSetConverter;
23 import com.google.crypto.tink.jwt.JwtInvalidException;
24 import com.google.crypto.tink.jwt.JwtMac;
25 import com.google.crypto.tink.jwt.JwtMacConfig;
26 import com.google.crypto.tink.jwt.JwtPublicKeySign;
27 import com.google.crypto.tink.jwt.JwtPublicKeyVerify;
28 import com.google.crypto.tink.jwt.JwtSignatureConfig;
29 import com.google.crypto.tink.jwt.JwtValidator;
30 import com.google.crypto.tink.jwt.RawJwt;
31 import com.google.crypto.tink.jwt.VerifiedJwt;
32 import com.google.crypto.tink.testing.proto.CreationRequest;
33 import com.google.crypto.tink.testing.proto.CreationResponse;
34 import com.google.crypto.tink.testing.proto.JwtClaimValue;
35 import com.google.crypto.tink.testing.proto.JwtFromJwkSetRequest;
36 import com.google.crypto.tink.testing.proto.JwtFromJwkSetResponse;
37 import com.google.crypto.tink.testing.proto.JwtGrpc.JwtImplBase;
38 import com.google.crypto.tink.testing.proto.JwtSignRequest;
39 import com.google.crypto.tink.testing.proto.JwtSignResponse;
40 import com.google.crypto.tink.testing.proto.JwtToJwkSetRequest;
41 import com.google.crypto.tink.testing.proto.JwtToJwkSetResponse;
42 import com.google.crypto.tink.testing.proto.JwtToken;
43 import com.google.crypto.tink.testing.proto.JwtVerifyRequest;
44 import com.google.crypto.tink.testing.proto.JwtVerifyResponse;
45 import com.google.crypto.tink.testing.proto.NullValue;
46 import com.google.protobuf.ByteString;
47 import com.google.protobuf.StringValue;
48 import com.google.protobuf.Timestamp;
49 import io.grpc.stub.StreamObserver;
50 import java.io.IOException;
51 import java.security.GeneralSecurityException;
52 import java.time.Clock;
53 import java.time.Duration;
54 import java.time.Instant;
55 import java.time.ZoneOffset;
56 import java.util.Map;
57 
58 /** Implements a gRPC JWT Testing service. */
59 public final class JwtServiceImpl extends JwtImplBase {
60 
JwtServiceImpl()61   public JwtServiceImpl() throws GeneralSecurityException {
62     JwtMacConfig.register();
63     JwtSignatureConfig.register();
64   }
65 
66   @Override
createJwtMac( CreationRequest request, StreamObserver<CreationResponse> responseObserver)67   public void createJwtMac(
68       CreationRequest request, StreamObserver<CreationResponse> responseObserver) {
69     Util.createPrimitiveForRpc(request, responseObserver, JwtMac.class);
70   }
71 
72   @Override
createJwtPublicKeySign( CreationRequest request, StreamObserver<CreationResponse> responseObserver)73   public void createJwtPublicKeySign(
74       CreationRequest request, StreamObserver<CreationResponse> responseObserver) {
75     Util.createPrimitiveForRpc(request, responseObserver, JwtPublicKeySign.class);
76   }
77 
78   @Override
createJwtPublicKeyVerify( CreationRequest request, StreamObserver<CreationResponse> responseObserver)79   public void createJwtPublicKeyVerify(
80       CreationRequest request, StreamObserver<CreationResponse> responseObserver) {
81     Util.createPrimitiveForRpc(request, responseObserver, JwtPublicKeyVerify.class);
82   }
83 
timestampToInstant(Timestamp t)84   private Instant timestampToInstant(Timestamp t) {
85     return Instant.ofEpochMilli(t.getSeconds() * 1000 + t.getNanos() / 1000000);
86   }
87 
instantToTimestamp(Instant i)88   private Timestamp instantToTimestamp(Instant i) {
89     long millis = i.toEpochMilli();
90     long seconds = millis / 1000;
91     int nanos = (int) ((millis - seconds * 1000) * 1000000);
92     return Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build();
93   }
94 
convertJwtTokenToRawJwt(JwtToken token)95   private RawJwt convertJwtTokenToRawJwt(JwtToken token) throws JwtInvalidException {
96     RawJwt.Builder rawJwtBuilder = RawJwt.newBuilder();
97     if (token.hasTypeHeader()) {
98       rawJwtBuilder.setTypeHeader(token.getTypeHeader().getValue());
99     }
100     if (token.hasIssuer()) {
101       rawJwtBuilder.setIssuer(token.getIssuer().getValue());
102     }
103     if (token.hasSubject()) {
104       rawJwtBuilder.setSubject(token.getSubject().getValue());
105     }
106     for (String audience : token.getAudiencesList()) {
107       rawJwtBuilder.addAudience(audience);
108     }
109     if (token.hasJwtId()) {
110       rawJwtBuilder.setJwtId(token.getJwtId().getValue());
111     }
112     if (token.hasExpiration()) {
113       rawJwtBuilder.setExpiration(timestampToInstant(token.getExpiration()));
114     } else {
115       rawJwtBuilder.withoutExpiration();
116     }
117     if (token.hasNotBefore()) {
118       rawJwtBuilder.setNotBefore(timestampToInstant(token.getNotBefore()));
119     }
120     if (token.hasIssuedAt()) {
121       rawJwtBuilder.setIssuedAt(timestampToInstant(token.getIssuedAt()));
122     }
123     for (Map.Entry<String, JwtClaimValue> entry : token.getCustomClaimsMap().entrySet()) {
124       String name = entry.getKey();
125       JwtClaimValue value = entry.getValue();
126       switch (value.getKindCase().getNumber()) {
127           case JwtClaimValue.NULL_VALUE_FIELD_NUMBER:
128               rawJwtBuilder.addNullClaim(name);
129           break;
130           case JwtClaimValue.BOOL_VALUE_FIELD_NUMBER:
131               rawJwtBuilder.addBooleanClaim(name, value.getBoolValue());
132           break;
133           case JwtClaimValue.NUMBER_VALUE_FIELD_NUMBER:
134               rawJwtBuilder.addNumberClaim(name, value.getNumberValue());
135           break;
136           case JwtClaimValue.STRING_VALUE_FIELD_NUMBER:
137               rawJwtBuilder.addStringClaim(name, value.getStringValue());
138           break;
139           case JwtClaimValue.JSON_ARRAY_VALUE_FIELD_NUMBER:
140               rawJwtBuilder.addJsonArrayClaim(name, value.getJsonArrayValue());
141           break;
142           case JwtClaimValue.JSON_OBJECT_VALUE_FIELD_NUMBER:
143               rawJwtBuilder.addJsonObjectClaim(name, value.getJsonObjectValue());
144           break;
145         default:
146           throw new RuntimeException("Unknown JwtClaimValue kind: " + value.getKindCase());
147       }
148     }
149     return rawJwtBuilder.build();
150   }
151 
computeMacAndEncode(JwtSignRequest request)152   private JwtSignResponse computeMacAndEncode(JwtSignRequest request)
153       throws GeneralSecurityException {
154     JwtMac jwtMac =
155         Util.parseBinaryProtoKeyset(request.getAnnotatedKeyset())
156             .getPrimitive(JwtMac.class);
157     try {
158       RawJwt rawJwt = convertJwtTokenToRawJwt(request.getRawJwt());
159       String signedCompactJwt = jwtMac.computeMacAndEncode(rawJwt);
160       return JwtSignResponse.newBuilder().setSignedCompactJwt(signedCompactJwt).build();
161     } catch (GeneralSecurityException e)  {
162       return JwtSignResponse.newBuilder().setErr(e.toString()).build();
163     }
164   }
165 
166   @Override
computeMacAndEncode( JwtSignRequest request, StreamObserver<JwtSignResponse> responseObserver)167   public void computeMacAndEncode(
168       JwtSignRequest request, StreamObserver<JwtSignResponse> responseObserver) {
169     try {
170       JwtSignResponse response = computeMacAndEncode(request);
171       responseObserver.onNext(response);
172       responseObserver.onCompleted();
173     } catch (Exception e) {
174       responseObserver.onError(e);
175     }
176   }
177 
publicKeySignAndEncode(JwtSignRequest request)178   private JwtSignResponse publicKeySignAndEncode(JwtSignRequest request)
179       throws GeneralSecurityException {
180     JwtPublicKeySign signer =
181         Util.parseBinaryProtoKeyset(request.getAnnotatedKeyset())
182             .getPrimitive(JwtPublicKeySign.class);
183     try {
184       RawJwt rawJwt = convertJwtTokenToRawJwt(request.getRawJwt());
185       String signedCompactJwt = signer.signAndEncode(rawJwt);
186       return JwtSignResponse.newBuilder().setSignedCompactJwt(signedCompactJwt).build();
187     } catch (GeneralSecurityException e)  {
188       return JwtSignResponse.newBuilder().setErr(e.toString()).build();
189     }
190   }
191 
192   @Override
publicKeySignAndEncode( JwtSignRequest request, StreamObserver<JwtSignResponse> responseObserver)193   public void publicKeySignAndEncode(
194       JwtSignRequest request, StreamObserver<JwtSignResponse> responseObserver) {
195     try {
196       JwtSignResponse response = publicKeySignAndEncode(request);
197       responseObserver.onNext(response);
198       responseObserver.onCompleted();
199     } catch (Exception e) {
200       responseObserver.onError(e);
201     }
202   }
203 
addCustomClaimToBuilder(VerifiedJwt token, String name, JwtToken.Builder builder)204   private void addCustomClaimToBuilder(VerifiedJwt token, String name, JwtToken.Builder builder)
205       throws JwtInvalidException {
206     // We do not know the type, so we just try them one by one.
207     if (token.isNullClaim(name)) {
208       builder.putCustomClaims(
209           name, JwtClaimValue.newBuilder().setNullValue(NullValue.NULL_VALUE).build());
210       return;
211     }
212     if (token.hasStringClaim(name)) {
213       String value = token.getStringClaim(name);
214       builder.putCustomClaims(name, JwtClaimValue.newBuilder().setStringValue(value).build());
215       return;
216     }
217     if (token.hasNumberClaim(name)) {
218       Double value = token.getNumberClaim(name);
219       builder.putCustomClaims(name, JwtClaimValue.newBuilder().setNumberValue(value).build());
220       return;
221     }
222     if (token.hasBooleanClaim(name)) {
223       Boolean value = token.getBooleanClaim(name);
224       builder.putCustomClaims(name, JwtClaimValue.newBuilder().setBoolValue(value).build());
225       return;
226     }
227     if (token.hasJsonArrayClaim(name)) {
228       String value = token.getJsonArrayClaim(name);
229       builder.putCustomClaims(name, JwtClaimValue.newBuilder().setJsonArrayValue(value).build());
230       return;
231     }
232     if (token.hasJsonObjectClaim(name)) {
233       String value = token.getJsonObjectClaim(name);
234       builder.putCustomClaims(name, JwtClaimValue.newBuilder().setJsonObjectValue(value).build());
235       return;
236     }
237     throw new RuntimeException("unable to add claim " + name);
238   }
239 
convertVerifiedJwtToJwtToken(VerifiedJwt verifiedJwt)240   private JwtToken convertVerifiedJwtToJwtToken(VerifiedJwt verifiedJwt)
241       throws JwtInvalidException {
242     JwtToken.Builder builder = JwtToken.newBuilder();
243     if (verifiedJwt.hasTypeHeader()) {
244       builder.setTypeHeader(StringValue.newBuilder().setValue(verifiedJwt.getTypeHeader()));
245     }
246     if (verifiedJwt.hasIssuer()) {
247         builder.setIssuer(StringValue.newBuilder().setValue(verifiedJwt.getIssuer()));
248     }
249     if (verifiedJwt.hasSubject()) {
250         builder.setSubject(StringValue.newBuilder().setValue(verifiedJwt.getSubject()));
251     }
252     if (verifiedJwt.hasAudiences()) {
253       for (String audience : verifiedJwt.getAudiences()) {
254         builder.addAudiences(audience);
255       }
256     }
257     if (verifiedJwt.hasJwtId()) {
258         builder.setJwtId(StringValue.newBuilder().setValue(verifiedJwt.getJwtId()));
259     }
260     if (verifiedJwt.hasExpiration()) {
261       builder.setExpiration(instantToTimestamp(verifiedJwt.getExpiration()));
262     }
263     if (verifiedJwt.hasNotBefore()) {
264       builder.setNotBefore(instantToTimestamp(verifiedJwt.getNotBefore()));
265     }
266     if (verifiedJwt.hasIssuedAt()) {
267       builder.setIssuedAt(instantToTimestamp(verifiedJwt.getIssuedAt()));
268     }
269     for (String claimName : verifiedJwt.customClaimNames()) {
270       addCustomClaimToBuilder(verifiedJwt, claimName, builder);
271     }
272     return builder.build();
273   }
274 
convertProtoValidatorToValidator( com.google.crypto.tink.testing.proto.JwtValidator validator)275   private JwtValidator convertProtoValidatorToValidator(
276       com.google.crypto.tink.testing.proto.JwtValidator validator) throws JwtInvalidException {
277     JwtValidator.Builder validatorBuilder = JwtValidator.newBuilder();
278     if (validator.hasExpectedTypeHeader()) {
279       validatorBuilder.expectTypeHeader(validator.getExpectedTypeHeader().getValue());
280     }
281     if (validator.hasExpectedIssuer()) {
282       validatorBuilder.expectIssuer(validator.getExpectedIssuer().getValue());
283     }
284     if (validator.hasExpectedAudience()) {
285       validatorBuilder.expectAudience(validator.getExpectedAudience().getValue());
286     }
287     if (validator.getIgnoreTypeHeader()) {
288       validatorBuilder.ignoreTypeHeader();
289     }
290     if (validator.getIgnoreIssuer()) {
291       validatorBuilder.ignoreIssuer();
292     }
293     if (validator.getIgnoreAudience()) {
294       validatorBuilder.ignoreAudiences();
295     }
296     if (validator.getAllowMissingExpiration()) {
297       validatorBuilder.allowMissingExpiration();
298     }
299     if (validator.getExpectIssuedInThePast()) {
300       validatorBuilder.expectIssuedInThePast();
301     }
302     if (validator.hasNow()) {
303       Instant now = timestampToInstant(validator.getNow());
304       validatorBuilder.setClock(Clock.fixed(now, ZoneOffset.UTC));
305     }
306     if (validator.hasClockSkew()) {
307       validatorBuilder.setClockSkew(Duration.ofSeconds(validator.getClockSkew().getSeconds()));
308     }
309     return validatorBuilder.build();
310   }
311 
verifyMacAndDecode(JwtVerifyRequest request)312   private JwtVerifyResponse verifyMacAndDecode(JwtVerifyRequest request)
313       throws GeneralSecurityException {
314     JwtMac jwtMac =
315         Util.parseBinaryProtoKeyset(request.getAnnotatedKeyset())
316             .getPrimitive(JwtMac.class);
317     try {
318       JwtValidator validator = convertProtoValidatorToValidator(request.getValidator());
319       VerifiedJwt verifiedJwt = jwtMac.verifyMacAndDecode(request.getSignedCompactJwt(), validator);
320       JwtToken token = convertVerifiedJwtToJwtToken(verifiedJwt);
321       return JwtVerifyResponse.newBuilder().setVerifiedJwt(token).build();
322     } catch (GeneralSecurityException e) {
323       return JwtVerifyResponse.newBuilder().setErr(e.toString()).build();
324     }
325   }
326 
327   @Override
verifyMacAndDecode( JwtVerifyRequest request, StreamObserver<JwtVerifyResponse> responseObserver)328   public void verifyMacAndDecode(
329       JwtVerifyRequest request,
330       StreamObserver<JwtVerifyResponse> responseObserver) {
331     try {
332       JwtVerifyResponse response = verifyMacAndDecode(request);
333       responseObserver.onNext(response);
334       responseObserver.onCompleted();
335     } catch (Exception e) {
336       responseObserver.onError(e);
337     }
338   }
339 
publicKeyVerifyAndDecode(JwtVerifyRequest request)340   private JwtVerifyResponse publicKeyVerifyAndDecode(JwtVerifyRequest request)
341       throws GeneralSecurityException {
342     JwtPublicKeyVerify verifier =
343         Util.parseBinaryProtoKeyset(request.getAnnotatedKeyset())
344             .getPrimitive(JwtPublicKeyVerify.class);
345     try {
346       JwtValidator validator = convertProtoValidatorToValidator(request.getValidator());
347       VerifiedJwt verifiedJwt = verifier.verifyAndDecode(request.getSignedCompactJwt(), validator);
348       JwtToken token = convertVerifiedJwtToJwtToken(verifiedJwt);
349       return JwtVerifyResponse.newBuilder().setVerifiedJwt(token).build();
350     } catch (GeneralSecurityException e) {
351       return JwtVerifyResponse.newBuilder().setErr(e.toString()).build();
352     }
353   }
354 
355   @Override
publicKeyVerifyAndDecode( JwtVerifyRequest request, StreamObserver<JwtVerifyResponse> responseObserver)356   public void publicKeyVerifyAndDecode(
357       JwtVerifyRequest request,
358       StreamObserver<JwtVerifyResponse> responseObserver) {
359     try {
360       JwtVerifyResponse response = publicKeyVerifyAndDecode(request);
361       responseObserver.onNext(response);
362       responseObserver.onCompleted();
363     } catch (GeneralSecurityException e) {
364       responseObserver.onError(e);
365     }
366   }
367 
368   /** Converts a Tink JWT Keyset to a JWK set. */
369   @Override
toJwkSet( JwtToJwkSetRequest request, StreamObserver<JwtToJwkSetResponse> responseObserver)370   public void toJwkSet(
371       JwtToJwkSetRequest request, StreamObserver<JwtToJwkSetResponse> responseObserver) {
372     JwtToJwkSetResponse response;
373     try {
374       KeysetHandle keysetHandle =
375           TinkProtoKeysetFormat.parseKeyset(
376               request.getKeyset().toByteArray(), InsecureSecretKeyAccess.get());
377       String jwkSet = JwkSetConverter.fromPublicKeysetHandle(keysetHandle);
378       response = JwtToJwkSetResponse.newBuilder().setJwkSet(jwkSet).build();
379     } catch (GeneralSecurityException | IOException e) {
380       response = JwtToJwkSetResponse.newBuilder().setErr(e.toString()).build();
381     }
382     responseObserver.onNext(response);
383     responseObserver.onCompleted();
384   }
385 
386   /** Converts a JWK set to a Tink JWT Keyset. */
387   @Override
fromJwkSet( JwtFromJwkSetRequest request, StreamObserver<JwtFromJwkSetResponse> responseObserver)388   public void fromJwkSet(
389       JwtFromJwkSetRequest request, StreamObserver<JwtFromJwkSetResponse> responseObserver) {
390     JwtFromJwkSetResponse response;
391     try {
392       KeysetHandle keysetHandle = JwkSetConverter.toPublicKeysetHandle(request.getJwkSet());
393       byte[] serializedKeyset =
394           TinkProtoKeysetFormat.serializeKeyset(keysetHandle, InsecureSecretKeyAccess.get());
395       response =
396           JwtFromJwkSetResponse.newBuilder()
397               .setKeyset(ByteString.copyFrom(serializedKeyset))
398               .build();
399     } catch (GeneralSecurityException | IOException e) {
400       response = JwtFromJwkSetResponse.newBuilder().setErr(e.toString()).build();
401       return;
402     }
403     responseObserver.onNext(response);
404     responseObserver.onCompleted();
405   }
406 }
407