For best performance, use the {@link GooglePaymentsPublicKeysManager#INSTANCE_PRODUCTION} for 31 | * production environment or {@link GooglePaymentsPublicKeysManager#INSTANCE_TEST} for test 32 | * environment. 33 | * 34 | *
If you need extra customizations for your use, we recommend you to use {@link 35 | * GooglePaymentsPublicKeysManager.Builder} to construct an instance and keep it as a singleton in a 36 | * static final variable across requests. 37 | * 38 | *
When initializing your server, we also recommend that you call {@link #refreshInBackground()} 39 | * to proactively fetch the keys. 40 | * 41 | * @since 1.0.0 42 | */ 43 | public class GooglePaymentsPublicKeysManager { 44 | /** Default HTTP transport used by this class. */ 45 | public static final NetHttpTransport DEFAULT_HTTP_TRANSPORT = 46 | new NetHttpTransport.Builder().build(); 47 | /** URL to fetch keys for environment production. */ 48 | public static final String KEYS_URL_PRODUCTION = 49 | "https://payments.developers.google.com/paymentmethodtoken/keys.json"; 50 | /** URL to fetch keys for environment test. */ 51 | public static final String KEYS_URL_TEST = 52 | "https://payments.developers.google.com/paymentmethodtoken/test/keys.json"; 53 | 54 | private static final Executor DEFAULT_BACKGROUND_EXECUTOR = Executors.newCachedThreadPool(); 55 | 56 | private final KeysDownloader downloader; 57 | 58 | /** 59 | * Instance configured to talk to fetch keys from production environment (from {@link 60 | * GooglePaymentsPublicKeysManager#KEYS_URL_PRODUCTION}). 61 | */ 62 | public static final GooglePaymentsPublicKeysManager INSTANCE_PRODUCTION = 63 | new GooglePaymentsPublicKeysManager( 64 | DEFAULT_BACKGROUND_EXECUTOR, DEFAULT_HTTP_TRANSPORT, KEYS_URL_PRODUCTION); 65 | /** 66 | * Instance configured to talk to fetch keys from test environment (from {@link 67 | * GooglePaymentsPublicKeysManager#KEYS_URL_TEST}). 68 | */ 69 | public static final GooglePaymentsPublicKeysManager INSTANCE_TEST = 70 | new GooglePaymentsPublicKeysManager( 71 | DEFAULT_BACKGROUND_EXECUTOR, DEFAULT_HTTP_TRANSPORT, KEYS_URL_TEST); 72 | 73 | GooglePaymentsPublicKeysManager( 74 | Executor backgroundExecutor, HttpTransport httpTransport, String keysUrl) { 75 | this.downloader = 76 | new KeysDownloader.Builder() 77 | .setUrl(keysUrl) 78 | .setExecutor(backgroundExecutor) 79 | .setHttpTransport(httpTransport) 80 | .build(); 81 | } 82 | 83 | HttpTransport getHttpTransport() { 84 | return downloader.getHttpTransport(); 85 | } 86 | 87 | String getUrl() { 88 | return downloader.getUrl(); 89 | } 90 | 91 | /** 92 | * Returns a string containing a JSON with the Google public signing keys. 93 | * 94 | *
Meant to be called by {@link PaymentMethodTokenRecipient}. 95 | */ 96 | String getTrustedSigningKeysJson() throws IOException { 97 | return this.downloader.download(); 98 | } 99 | 100 | /** Fetches keys in the background. */ 101 | public void refreshInBackground() { 102 | downloader.refreshInBackground(); 103 | } 104 | 105 | /** 106 | * Builder for {@link GooglePaymentsPublicKeysManager}. 107 | * 108 | * @since 1.0.0 109 | */ 110 | public static class Builder { 111 | private HttpTransport httpTransport = DEFAULT_HTTP_TRANSPORT; 112 | private String keysUrl = KEYS_URL_PRODUCTION; 113 | 114 | @CanIgnoreReturnValue 115 | public Builder setKeysUrl(String keysUrl) { 116 | this.keysUrl = keysUrl; 117 | return this; 118 | } 119 | 120 | /** 121 | * Sets the HTTP transport. 122 | * 123 | *
You generally should not need to set a custom transport as the default transport {@link 124 | * GooglePaymentsPublicKeysManager#DEFAULT_HTTP_TRANSPORT} should be suited for most use cases. 125 | */ 126 | @CanIgnoreReturnValue 127 | public Builder setHttpTransport(HttpTransport httpTransport) { 128 | this.httpTransport = httpTransport; 129 | return this; 130 | } 131 | 132 | public GooglePaymentsPublicKeysManager build() { 133 | // If all parameters are equal to the existing singleton instances, returning them instead. 134 | // This is more a safe guard if users of this class construct a new class and forget to 135 | // save in a singleton. 136 | for (GooglePaymentsPublicKeysManager instance : 137 | Arrays.asList(INSTANCE_PRODUCTION, INSTANCE_TEST)) { 138 | if (instance.getHttpTransport() == httpTransport && instance.getUrl().equals(keysUrl)) { 139 | return instance; 140 | } 141 | } 142 | return new GooglePaymentsPublicKeysManager( 143 | DEFAULT_BACKGROUND_EXECUTOR, httpTransport, keysUrl); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/JwtKeyConverter.java: -------------------------------------------------------------------------------- 1 | // Copyright 2023 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.apps.paymentmethodtoken; 18 | 19 | import com.google.crypto.tink.AccessesPartialKey; 20 | import com.google.crypto.tink.SecretKeyAccess; 21 | import com.google.crypto.tink.jwt.JwtEcdsaParameters; 22 | import com.google.crypto.tink.jwt.JwtEcdsaPrivateKey; 23 | import com.google.crypto.tink.jwt.JwtEcdsaPublicKey; 24 | import com.google.crypto.tink.subtle.Base64; 25 | import com.google.crypto.tink.subtle.EllipticCurves; 26 | import com.google.crypto.tink.util.SecretBigInteger; 27 | import java.security.GeneralSecurityException; 28 | import java.security.interfaces.ECPrivateKey; 29 | import java.security.interfaces.ECPublicKey; 30 | 31 | /** 32 | * Functions that convert raw keys into Tink JWT Signature keys. 33 | * 34 | *
These functions are currently just an example, and are not (yet) part of the public API. 35 | */ 36 | public final class JwtKeyConverter { 37 | 38 | /** 39 | * Converts an uncompressed Base64 encoded ECDSA public key for NIST P-256 Curve into a Tink 40 | * JwtEcdsaPublicKey with algorithm ES256. 41 | */ 42 | @AccessesPartialKey 43 | public static JwtEcdsaPublicKey fromBase64EncodedNistP256PublicKey( 44 | String based64EncodedEcNistP256PublicKey) throws GeneralSecurityException { 45 | ECPublicKey ecPublicKey = 46 | EllipticCurves.getEcPublicKey( 47 | EllipticCurves.CurveType.NIST_P256, 48 | EllipticCurves.PointFormatType.UNCOMPRESSED, 49 | Base64.decode(based64EncodedEcNistP256PublicKey)); 50 | JwtEcdsaParameters parameters = 51 | JwtEcdsaParameters.builder() 52 | .setKidStrategy(JwtEcdsaParameters.KidStrategy.IGNORED) 53 | .setAlgorithm(JwtEcdsaParameters.Algorithm.ES256) 54 | .build(); 55 | return JwtEcdsaPublicKey.builder() 56 | .setParameters(parameters) 57 | .setPublicPoint(ecPublicKey.getW()) 58 | .build(); 59 | } 60 | 61 | /** 62 | * Converts a JwtEcdsaPublicKey using algorithm ES256 to an uncompressed Base64 encoded ECDSA 63 | * public key for NIST P-256 Curve. 64 | */ 65 | @AccessesPartialKey 66 | public static String toBase64EncodedNistP256PublicKey(JwtEcdsaPublicKey publicKey) 67 | throws GeneralSecurityException { 68 | if (publicKey.getParameters().getAlgorithm() != JwtEcdsaParameters.Algorithm.ES256) { 69 | throw new GeneralSecurityException("Only ES256 is supported."); 70 | } 71 | if (publicKey.getParameters().getKidStrategy() != JwtEcdsaParameters.KidStrategy.IGNORED) { 72 | throw new GeneralSecurityException("Only KidStrategy IGNORED is supported."); 73 | } 74 | return Base64.encode( 75 | EllipticCurves.pointEncode( 76 | EllipticCurves.CurveType.NIST_P256, 77 | EllipticCurves.PointFormatType.UNCOMPRESSED, 78 | publicKey.getPublicPoint())); 79 | } 80 | 81 | /** 82 | * Converts a Base64 encoded PKCS8 ECDSA private key for NIST P-256 Curve into a Tink 83 | * JwtEcdsaPrivateKey with algorithm ES256. 84 | * 85 | *
It also requires that you provide the corresponding JwtEcdsaPublicKey. 86 | */ 87 | @AccessesPartialKey 88 | public static JwtEcdsaPrivateKey fromBased64EncodedPkcs8EcNistP256PrivateKey( 89 | String based64EncodedPkcs8EcNistP256PrivateKey, 90 | JwtEcdsaPublicKey publicKey, 91 | SecretKeyAccess access) 92 | throws GeneralSecurityException { 93 | if (publicKey.getParameters().getAlgorithm() != JwtEcdsaParameters.Algorithm.ES256) { 94 | throw new GeneralSecurityException("Only ES256 is supported."); 95 | } 96 | if (publicKey.getParameters().getKidStrategy() != JwtEcdsaParameters.KidStrategy.IGNORED) { 97 | throw new GeneralSecurityException("Only KidStrategy IGNORED is supported."); 98 | } 99 | ECPrivateKey ecPrivateKey = 100 | EllipticCurves.getEcPrivateKey(Base64.decode(based64EncodedPkcs8EcNistP256PrivateKey)); 101 | return JwtEcdsaPrivateKey.create( 102 | publicKey, SecretBigInteger.fromBigInteger(ecPrivateKey.getS(), access)); 103 | } 104 | 105 | private JwtKeyConverter() {} 106 | } 107 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenConstants.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 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.apps.paymentmethodtoken; 18 | 19 | import com.google.crypto.tink.subtle.EllipticCurves; 20 | import com.google.crypto.tink.subtle.Enums.HashType; 21 | import java.nio.charset.StandardCharsets; 22 | 23 | /** Various constants. */ 24 | final class PaymentMethodTokenConstants { 25 | public static final String GOOGLE_SENDER_ID = "Google"; 26 | public static final String HMAC_SHA256_ALGO = "HmacSha256"; 27 | public static final byte[] HKDF_EMPTY_SALT = new byte[0]; 28 | public static final byte[] GOOGLE_CONTEXT_INFO_ECV1 = "Google".getBytes(StandardCharsets.UTF_8); 29 | public static final String AES_CTR_ALGO = "AES/CTR/NoPadding"; 30 | // Zero IV is fine here because each encryption uses a unique key. 31 | public static final byte[] AES_CTR_ZERO_IV = new byte[16]; 32 | public static final EllipticCurves.CurveType P256_CURVE_TYPE = EllipticCurves.CurveType.NIST_P256; 33 | public static final EllipticCurves.PointFormatType UNCOMPRESSED_POINT_FORMAT = 34 | EllipticCurves.PointFormatType.UNCOMPRESSED; 35 | public static final String PROTOCOL_VERSION_EC_V1 = "ECv1"; 36 | public static final String PROTOCOL_VERSION_EC_V2 = "ECv2"; 37 | public static final String PROTOCOL_VERSION_EC_V2_SIGNING_ONLY = "ECv2SigningOnly"; 38 | public static final HashType ECDSA_HASH_SHA256 = HashType.SHA256; 39 | 40 | public static final String JSON_ENCRYPTED_MESSAGE_KEY = "encryptedMessage"; 41 | public static final String JSON_EPHEMERAL_PUBLIC_KEY = "ephemeralPublicKey"; 42 | public static final String JSON_INTERMEDIATE_SIGNING_KEY = "intermediateSigningKey"; 43 | public static final String JSON_KEY_EXPIRATION_KEY = "keyExpiration"; 44 | public static final String JSON_KEY_VALUE_KEY = "keyValue"; 45 | public static final String JSON_MESSAGE_EXPIRATION_KEY = "messageExpiration"; 46 | public static final String JSON_PROTOCOL_VERSION_KEY = "protocolVersion"; 47 | public static final String JSON_SIGNATURES_KEY = "signatures"; 48 | public static final String JSON_SIGNATURE_KEY = "signature"; 49 | public static final String JSON_SIGNED_KEY_KEY = "signedKey"; 50 | public static final String JSON_SIGNED_MESSAGE_KEY = "signedMessage"; 51 | public static final String JSON_TAG_KEY = "tag"; 52 | 53 | /** Represents configuration regarding each protocol version. */ 54 | enum ProtocolVersionConfig { 55 | EC_V1( 56 | /* protocolVersion= */ PROTOCOL_VERSION_EC_V1, 57 | /* aesCtrKeySize= */ 128 / 8, 58 | /* hmacSha256KeySize= */ 128 / 8, 59 | /* isEncryptionRequired= */ true, 60 | /* supportsIntermediateSigningKeys= */ false), 61 | EC_V2( 62 | /* protocolVersion= */ PROTOCOL_VERSION_EC_V2, 63 | /* aesCtrKeySize= */ 256 / 8, 64 | /* hmacSha256KeySize= */ 256 / 8, 65 | /* isEncryptionRequired= */ true, 66 | /* supportsIntermediateSigningKeys= */ true), 67 | EC_V2_SIGNING_ONLY( 68 | /* protocolVersion= */ PROTOCOL_VERSION_EC_V2_SIGNING_ONLY, 69 | /* aesCtrKeySize= */ 256 / 8, 70 | /* hmacSha256KeySize= */ 256 / 8, 71 | /* isEncryptionRequired= */ false, 72 | /* supportsIntermediateSigningKeys= */ true); 73 | 74 | public final String protocolVersion; 75 | public final int aesCtrKeySize; 76 | public final int hmacSha256KeySize; 77 | public final boolean isEncryptionRequired; 78 | public final boolean supportsIntermediateSigningKeys; 79 | 80 | ProtocolVersionConfig( 81 | String protocolVersion, 82 | int aesCtrKeySize, 83 | int hmacSha256KeySize, 84 | boolean isEncryptionRequired, 85 | boolean supportsIntermediateSigningKeys) { 86 | this.protocolVersion = protocolVersion; 87 | this.aesCtrKeySize = aesCtrKeySize; 88 | this.hmacSha256KeySize = hmacSha256KeySize; 89 | this.isEncryptionRequired = isEncryptionRequired; 90 | this.supportsIntermediateSigningKeys = supportsIntermediateSigningKeys; 91 | } 92 | 93 | public static ProtocolVersionConfig forProtocolVersion(String protocolVersion) { 94 | for (ProtocolVersionConfig protocolVersionConfig : ProtocolVersionConfig.values()) { 95 | if (protocolVersionConfig.protocolVersion.equals(protocolVersion)) { 96 | return protocolVersionConfig; 97 | } 98 | } 99 | throw new IllegalArgumentException("Unknown protocol version: " + protocolVersion); 100 | } 101 | } 102 | 103 | private PaymentMethodTokenConstants() {} 104 | } 105 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridDecrypt.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 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.apps.paymentmethodtoken; 18 | 19 | import static java.nio.charset.StandardCharsets.UTF_8; 20 | 21 | import com.google.crypto.tink.HybridDecrypt; 22 | import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig; 23 | import com.google.crypto.tink.subtle.Base64; 24 | import com.google.crypto.tink.subtle.Bytes; 25 | import com.google.crypto.tink.subtle.EllipticCurves; 26 | import com.google.crypto.tink.subtle.Hkdf; 27 | import com.google.gson.JsonObject; 28 | import com.google.gson.JsonParseException; 29 | import com.google.gson.JsonParser; 30 | import java.security.GeneralSecurityException; 31 | import java.security.interfaces.ECPrivateKey; 32 | import java.security.interfaces.ECPublicKey; 33 | import java.util.Arrays; 34 | 35 | /** 36 | * A {@link HybridDecrypt} implementation for the hybrid encryption used in Google Payment Method 38 | * Token. 39 | */ 40 | class PaymentMethodTokenHybridDecrypt implements HybridDecrypt { 41 | private final PaymentMethodTokenRecipientKem recipientKem; 42 | private final ProtocolVersionConfig protocolVersionConfig; 43 | 44 | PaymentMethodTokenHybridDecrypt( 45 | final ECPrivateKey recipientPrivateKey, ProtocolVersionConfig protocolVersionConfig) 46 | throws GeneralSecurityException { 47 | this( 48 | new PaymentMethodTokenRecipientKem() { 49 | @Override 50 | public byte[] computeSharedSecret(final byte[] ephemeralPublicKey) 51 | throws GeneralSecurityException { 52 | ECPublicKey publicKey = 53 | EllipticCurves.getEcPublicKey( 54 | recipientPrivateKey.getParams(), 55 | PaymentMethodTokenConstants.UNCOMPRESSED_POINT_FORMAT, 56 | ephemeralPublicKey); 57 | return EllipticCurves.computeSharedSecret(recipientPrivateKey, publicKey); 58 | } 59 | }, 60 | protocolVersionConfig); 61 | } 62 | 63 | PaymentMethodTokenHybridDecrypt( 64 | final PaymentMethodTokenRecipientKem recipientKem, 65 | ProtocolVersionConfig protocolVersionConfig) { 66 | this.recipientKem = recipientKem; 67 | this.protocolVersionConfig = protocolVersionConfig; 68 | } 69 | 70 | @Override 71 | public byte[] decrypt(final byte[] ciphertext, final byte[] contextInfo) 72 | throws GeneralSecurityException { 73 | try { 74 | JsonObject json = JsonParser.parseString(new String(ciphertext, UTF_8)).getAsJsonObject(); 75 | validate(json); 76 | byte[] demKey = kem(json, contextInfo); 77 | return dem(json, demKey); 78 | } catch (JsonParseException | IllegalStateException e) { 79 | throw new GeneralSecurityException("cannot decrypt; failed to parse JSON", e); 80 | } 81 | } 82 | 83 | private byte[] kem(JsonObject json, final byte[] contextInfo) throws GeneralSecurityException { 84 | int demKeySize = protocolVersionConfig.aesCtrKeySize + protocolVersionConfig.hmacSha256KeySize; 85 | byte[] ephemeralPublicKey = 86 | Base64.decode( 87 | json.get(PaymentMethodTokenConstants.JSON_EPHEMERAL_PUBLIC_KEY).getAsString()); 88 | byte[] sharedSecret = recipientKem.computeSharedSecret(ephemeralPublicKey); 89 | return Hkdf.computeEciesHkdfSymmetricKey( 90 | ephemeralPublicKey, 91 | sharedSecret, 92 | PaymentMethodTokenConstants.HMAC_SHA256_ALGO, 93 | PaymentMethodTokenConstants.HKDF_EMPTY_SALT, 94 | contextInfo, 95 | demKeySize); 96 | } 97 | 98 | private byte[] dem(JsonObject json, final byte[] demKey) throws GeneralSecurityException { 99 | byte[] hmacSha256Key = 100 | Arrays.copyOfRange(demKey, protocolVersionConfig.aesCtrKeySize, demKey.length); 101 | byte[] encryptedMessage = 102 | Base64.decode( 103 | json.get(PaymentMethodTokenConstants.JSON_ENCRYPTED_MESSAGE_KEY).getAsString()); 104 | byte[] computedTag = PaymentMethodTokenUtil.hmacSha256(hmacSha256Key, encryptedMessage); 105 | byte[] expectedTag = 106 | Base64.decode(json.get(PaymentMethodTokenConstants.JSON_TAG_KEY).getAsString()); 107 | if (!Bytes.equal(expectedTag, computedTag)) { 108 | throw new GeneralSecurityException("cannot decrypt; invalid MAC"); 109 | } 110 | byte[] aesCtrKey = Arrays.copyOf(demKey, protocolVersionConfig.aesCtrKeySize); 111 | return PaymentMethodTokenUtil.aesCtr(aesCtrKey, encryptedMessage); 112 | } 113 | 114 | private void validate(JsonObject payload) throws GeneralSecurityException { 115 | if (!payload.has(PaymentMethodTokenConstants.JSON_ENCRYPTED_MESSAGE_KEY) 116 | || !payload.has(PaymentMethodTokenConstants.JSON_TAG_KEY) 117 | || !payload.has(PaymentMethodTokenConstants.JSON_EPHEMERAL_PUBLIC_KEY) 118 | || payload.size() != 3) { 119 | throw new GeneralSecurityException( 120 | "The payload must contain exactly encryptedMessage, tag and ephemeralPublicKey"); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridEncrypt.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 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.apps.paymentmethodtoken; 18 | 19 | import static java.nio.charset.StandardCharsets.UTF_8; 20 | 21 | import com.google.crypto.tink.HybridEncrypt; 22 | import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig; 23 | import com.google.crypto.tink.subtle.Base64; 24 | import com.google.crypto.tink.subtle.EciesHkdfSenderKem; 25 | import com.google.gson.JsonObject; 26 | import com.google.gson.internal.Streams; 27 | import com.google.gson.stream.JsonWriter; 28 | import java.io.IOException; 29 | import java.io.StringWriter; 30 | import java.security.GeneralSecurityException; 31 | import java.security.interfaces.ECPublicKey; 32 | import java.util.Arrays; 33 | 34 | /** 35 | * A {@link HybridEncrypt} implementation for the hybrid encryption used in Google Payment Method 37 | * Token. 38 | */ 39 | class PaymentMethodTokenHybridEncrypt implements HybridEncrypt { 40 | private final EciesHkdfSenderKem senderKem; 41 | private final ProtocolVersionConfig protocolVersionConfig; 42 | 43 | public PaymentMethodTokenHybridEncrypt( 44 | final ECPublicKey recipientPublicKey, final ProtocolVersionConfig protocolVersionConfig) { 45 | this.senderKem = new EciesHkdfSenderKem(recipientPublicKey); 46 | this.protocolVersionConfig = protocolVersionConfig; 47 | } 48 | 49 | static String jsonEncodeCiphertext(byte[] ciphertext, byte[] tag, byte[] ephemeralPublicKey) 50 | throws GeneralSecurityException { 51 | JsonObject result = new JsonObject(); 52 | result.addProperty( 53 | PaymentMethodTokenConstants.JSON_ENCRYPTED_MESSAGE_KEY, Base64.encode(ciphertext)); 54 | result.addProperty( 55 | PaymentMethodTokenConstants.JSON_EPHEMERAL_PUBLIC_KEY, Base64.encode(ephemeralPublicKey)); 56 | result.addProperty(PaymentMethodTokenConstants.JSON_TAG_KEY, Base64.encode(tag)); 57 | StringWriter stringWriter = new StringWriter(); 58 | JsonWriter jsonWriter = new JsonWriter(stringWriter); 59 | jsonWriter.setHtmlSafe(true); 60 | try { 61 | Streams.write(result, jsonWriter); 62 | return stringWriter.toString(); 63 | } catch (IOException e) { 64 | throw new GeneralSecurityException("cannot encrypt; JSON error", e); 65 | } 66 | } 67 | 68 | @Override 69 | public byte[] encrypt(final byte[] plaintext, final byte[] contextInfo) 70 | throws GeneralSecurityException { 71 | int symmetricKeySize = 72 | protocolVersionConfig.aesCtrKeySize + protocolVersionConfig.hmacSha256KeySize; 73 | EciesHkdfSenderKem.KemKey kemKey = 74 | senderKem.generateKey( 75 | PaymentMethodTokenConstants.HMAC_SHA256_ALGO, 76 | PaymentMethodTokenConstants.HKDF_EMPTY_SALT, 77 | contextInfo, 78 | symmetricKeySize, 79 | PaymentMethodTokenConstants.UNCOMPRESSED_POINT_FORMAT); 80 | byte[] aesCtrKey = Arrays.copyOf(kemKey.getSymmetricKey(), protocolVersionConfig.aesCtrKeySize); 81 | byte[] ciphertext = PaymentMethodTokenUtil.aesCtr(aesCtrKey, plaintext); 82 | byte[] hmacSha256Key = 83 | Arrays.copyOfRange( 84 | kemKey.getSymmetricKey(), protocolVersionConfig.aesCtrKeySize, symmetricKeySize); 85 | byte[] tag = PaymentMethodTokenUtil.hmacSha256(hmacSha256Key, ciphertext); 86 | byte[] ephemeralPublicKey = kemKey.getKemBytes(); 87 | 88 | String jsonEncodedCiphertext = jsonEncodeCiphertext(ciphertext, tag, ephemeralPublicKey); 89 | return jsonEncodedCiphertext.getBytes(UTF_8); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientKem.java: -------------------------------------------------------------------------------- 1 | // Copyright 2018 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.apps.paymentmethodtoken; 18 | 19 | import java.security.GeneralSecurityException; 20 | 21 | /** 22 | * Interface for recipient's key encapsulation mechanism (KEM). 23 | * 24 | *
Google Pay's tokens are encrypted using ECIES which is a hybrid encryption mode consisting of 25 | * two steps: key encapsulation mechanisam (KEM) using Elliptic Curve Diffie Hellman (ECDH) and HKDF 26 | * and data encapsulation mechanism (DEM) using AES-CTR-HMAC. 27 | * 28 | *
During encryption, the KEM step takes the recipient's public key and produces a DEM key and an 29 | * ephemeral public key. The DEM key is then used to encrypt the credit card data, and the ephemeral 30 | * public key is sent as the ephemeralPublicKey field of the payload. 31 | * 32 | *
To decrypt, the recipient must use their private key to compute an ECDH shared secret from the 33 | * ephemeral public key, and from that derive the DEM key using HKDF. If the recipient keeps the 34 | * private key in a HSM, they cannot load the private key in Tink, but they can implement this 35 | * interface and configure Tink to use their custom KEM implementation with {@link 36 | * PaymentMethodTokenRecipient.Builder#addRecipientKem}. 37 | * 38 | * @see Google Payment 39 | * Method Token standard 40 | * @since 1.1.0 41 | */ 42 | public interface PaymentMethodTokenRecipientKem { 43 | /** 44 | * Computes a shared secret from the {@code ephemeralPublicKey}, using ECDH. 45 | * 46 | *
{@code ephemeralPublicKey} is a point on the elliptic curve defined in the Google Payment Method 48 | * Token standard, encoded in uncompressed point format. In version ECv1 and ECv2 of the 49 | * standard, the elliptic curve is NIST P-256. 50 | * 51 | *
Note that you only needs to compute the shared secret, but you don't have to derive the DEM 52 | * key with HKDF -- that process is handled by Tink. 53 | */ 54 | byte[] computeSharedSecret(final byte[] ephemeralPublicKey) throws GeneralSecurityException; 55 | } 56 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientKeyGen.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 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.apps.paymentmethodtoken; 18 | 19 | import com.google.crypto.tink.subtle.Base64; 20 | import com.google.crypto.tink.subtle.EllipticCurves; 21 | import java.io.File; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.security.GeneralSecurityException; 25 | import java.security.KeyPair; 26 | import java.security.interfaces.ECPublicKey; 27 | 28 | /** 29 | * A util that generates key pairs for the recipient side of Google 31 | * Payment Method Token. 32 | * 33 | *
36 | * bazel build apps/paymentmethodtoken/... 37 | * ./bazel-bin/apps/paymentmethodtoken/recipientkeygen 38 | *39 | * 40 | *
Running that command will generate a fresh key pair. The private/public key can be found in
41 | * private_key.bin/public_key.bin. The content of private_key.bin can be passed to {@link
42 | * PaymentMethodTokenRecipient.Builder#addRecipientPrivateKey} and the content of public_key.bin can
43 | * be passed to {@link PaymentMethodTokenSender.Builder#rawUncompressedRecipientPublicKey}.
44 | */
45 | public final class PaymentMethodTokenRecipientKeyGen {
46 | private static final String PRIVATE_KEY_FILE = "private_key.bin";
47 |
48 | private static final String PUBLIC_KEY_FILE = "public_key.bin";
49 |
50 | private static void generateKey() throws GeneralSecurityException, IOException {
51 | KeyPair keyPair = EllipticCurves.generateKeyPair(PaymentMethodTokenConstants.P256_CURVE_TYPE);
52 | writeBase64(PRIVATE_KEY_FILE, keyPair.getPrivate().getEncoded());
53 |
54 | ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
55 | writeBase64(
56 | PUBLIC_KEY_FILE,
57 | EllipticCurves.pointEncode(
58 | PaymentMethodTokenConstants.P256_CURVE_TYPE,
59 | PaymentMethodTokenConstants.UNCOMPRESSED_POINT_FORMAT,
60 | publicKey.getW()));
61 | }
62 |
63 | private static void writeBase64(String pathname, byte[] content) throws IOException {
64 | File out = new File(pathname);
65 | if (out.exists()) {
66 | System.out.println("Please make sure that " + pathname + " does not exist.");
67 | System.exit(-1);
68 | }
69 | FileOutputStream stream = new FileOutputStream(out);
70 | stream.write(Base64.encode(content, Base64.DEFAULT | Base64.NO_WRAP));
71 | stream.close();
72 | }
73 |
74 | public static void main(String[] args) throws GeneralSecurityException, IOException {
75 | System.out.println("Generating key....");
76 | generateKey();
77 | System.out.println("done.");
78 | }
79 |
80 | private PaymentMethodTokenRecipientKeyGen() {}
81 | }
82 |
--------------------------------------------------------------------------------
/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenUtil.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
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.apps.paymentmethodtoken;
18 |
19 | import com.google.crypto.tink.subtle.Base64;
20 | import com.google.crypto.tink.subtle.Bytes;
21 | import com.google.crypto.tink.subtle.EllipticCurves;
22 | import com.google.crypto.tink.subtle.EngineFactory;
23 | import java.nio.charset.StandardCharsets;
24 | import java.security.GeneralSecurityException;
25 | import java.security.interfaces.ECPrivateKey;
26 | import java.security.interfaces.ECPublicKey;
27 | import javax.crypto.Cipher;
28 | import javax.crypto.Mac;
29 | import javax.crypto.spec.IvParameterSpec;
30 | import javax.crypto.spec.SecretKeySpec;
31 |
32 | /** Various helpers. */
33 | final class PaymentMethodTokenUtil {
34 | static ECPublicKey rawUncompressedEcPublicKey(String rawUncompressedPublicKey)
35 | throws GeneralSecurityException {
36 | return EllipticCurves.getEcPublicKey(
37 | PaymentMethodTokenConstants.P256_CURVE_TYPE,
38 | PaymentMethodTokenConstants.UNCOMPRESSED_POINT_FORMAT,
39 | Base64.decode(rawUncompressedPublicKey));
40 | }
41 |
42 | static ECPublicKey x509EcPublicKey(String x509PublicKey) throws GeneralSecurityException {
43 | return EllipticCurves.getEcPublicKey(Base64.decode(x509PublicKey));
44 | }
45 |
46 | static ECPrivateKey pkcs8EcPrivateKey(String pkcs8PrivateKey) throws GeneralSecurityException {
47 | return EllipticCurves.getEcPrivateKey(Base64.decode(pkcs8PrivateKey));
48 | }
49 |
50 | static byte[] toLengthValue(String... chunks) throws GeneralSecurityException {
51 | byte[] out = new byte[0];
52 | for (String chunk : chunks) {
53 | byte[] bytes = chunk.getBytes(StandardCharsets.UTF_8);
54 | out = Bytes.concat(out, Bytes.intToByteArray(4, bytes.length));
55 | out = Bytes.concat(out, bytes);
56 | }
57 | return out;
58 | }
59 |
60 | static byte[] aesCtr(final byte[] encryptionKey, final byte[] message)
61 | throws GeneralSecurityException {
62 | Cipher cipher = EngineFactory.CIPHER.getInstance(PaymentMethodTokenConstants.AES_CTR_ALGO);
63 | cipher.init(
64 | Cipher.ENCRYPT_MODE,
65 | new SecretKeySpec(encryptionKey, "AES"),
66 | new IvParameterSpec(PaymentMethodTokenConstants.AES_CTR_ZERO_IV));
67 | return cipher.doFinal(message);
68 | }
69 |
70 | static byte[] hmacSha256(final byte[] macKey, final byte[] encryptedMessage)
71 | throws GeneralSecurityException {
72 | SecretKeySpec key = new SecretKeySpec(macKey, PaymentMethodTokenConstants.HMAC_SHA256_ALGO);
73 | Mac mac = EngineFactory.MAC.getInstance(PaymentMethodTokenConstants.HMAC_SHA256_ALGO);
74 | mac.init(key);
75 | return mac.doFinal(encryptedMessage);
76 | }
77 |
78 | private PaymentMethodTokenUtil() {}
79 | }
80 |
--------------------------------------------------------------------------------
/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/SenderIntermediateCertFactory.java:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
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.apps.paymentmethodtoken;
18 |
19 | import com.google.crypto.tink.PublicKeySign;
20 | import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig;
21 | import com.google.crypto.tink.subtle.Base64;
22 | import com.google.crypto.tink.subtle.EcdsaSignJce;
23 | import com.google.crypto.tink.subtle.EllipticCurves.EcdsaEncoding;
24 | import com.google.errorprone.annotations.CanIgnoreReturnValue;
25 | import com.google.gson.JsonArray;
26 | import com.google.gson.JsonObject;
27 | import com.google.gson.JsonParseException;
28 | import com.google.gson.internal.Streams;
29 | import com.google.gson.stream.JsonWriter;
30 | import java.io.IOException;
31 | import java.io.StringWriter;
32 | import java.security.GeneralSecurityException;
33 | import java.security.interfaces.ECPrivateKey;
34 | import java.util.ArrayList;
35 | import java.util.List;
36 |
37 | /**
38 | * Creates a signed certificate with the intermediate signing keys used by the sender in certain
39 | * protocol versions.
40 | *
41 | * @since 1.1.0
42 | */
43 | public class SenderIntermediateCertFactory {
44 | private final List It must be base64 encoded PKCS8 private key.
122 | */
123 | @CanIgnoreReturnValue
124 | public Builder addSenderSigningKey(String val) throws GeneralSecurityException {
125 | return addSenderSigningKey(PaymentMethodTokenUtil.pkcs8EcPrivateKey(val));
126 | }
127 |
128 | /**
129 | * Adds a signing key of the sender.
130 | *
131 | * It must be base64 encoded PKCS8 private key.
132 | */
133 | @CanIgnoreReturnValue
134 | public Builder addSenderSigningKey(ECPrivateKey val) throws GeneralSecurityException {
135 | this.senderSigningKeys.add(val);
136 | return this;
137 | }
138 |
139 | /**
140 | * Sets the intermediate signing key being signed.
141 | *
142 | * The public key specified here is a base64 (no wrapping, padded) version of the key encoded
143 | * in ASN.1 type SubjectPublicKeyInfo defined in the X.509 standard.
144 | */
145 | @CanIgnoreReturnValue
146 | public Builder senderIntermediateSigningKey(String val) throws GeneralSecurityException {
147 | // Parsing to validate the format
148 | Object unused = PaymentMethodTokenUtil.x509EcPublicKey(val);
149 | intermediateSigningKey = val;
150 | return this;
151 | }
152 |
153 | public SenderIntermediateCertFactory build() throws GeneralSecurityException {
154 | return new SenderIntermediateCertFactory(
155 | protocolVersion, senderId, senderSigningKeys, intermediateSigningKey, expiration);
156 | }
157 | }
158 |
159 | static String jsonEncodeSignedKey(String intermediateSigningKey, long expiration) {
160 | try {
161 | JsonObject jsonObj = new JsonObject();
162 | jsonObj.addProperty(PaymentMethodTokenConstants.JSON_KEY_VALUE_KEY, intermediateSigningKey);
163 | jsonObj.addProperty(
164 | PaymentMethodTokenConstants.JSON_KEY_EXPIRATION_KEY, Long.toString(expiration));
165 | StringWriter stringWriter = new StringWriter();
166 | JsonWriter jsonWriter = new JsonWriter(stringWriter);
167 | jsonWriter.setHtmlSafe(true);
168 | Streams.write(jsonObj, jsonWriter);
169 | return stringWriter.toString();
170 | } catch (JsonParseException | IllegalStateException | IOException e) {
171 | throw new AssertionError("Failed to perform JSON encoding", e);
172 | }
173 | }
174 |
175 | static String jsonEncodeCertificate(String signedKey, ArrayList This will return a serialized JSONObject in the following format:
198 | *
199 | * https://developers.google.com/pay/api/web/guides/resources/payment-data-cryptography
29 | */
30 | @RunWith(JUnit4.class)
31 | public final class ExampleTest {
32 |
33 | /**
34 | * Sample Google private signing key for the ECv2 protocolVersion.
35 | *
36 | * Corresponds to one of the public keys in {@link #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON}.
37 | */
38 | private static final String GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64 =
39 | "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKvEdSS8f0mjTCNKev"
40 | + "aKXIzfNC5b4A104gJWI9TsLIMqhRANCAAT/X7ccFVJt2/6Ps1oCt2AzKhIAz"
41 | + "jfJHJ3Op2DVPGh1LMD3oOPgxzUSIqujHG6dq9Ui93Eacl4VWJPMW/MVHHIL";
42 |
43 | /** Sample Google provided JSON with its public signing keys. */
44 | private static final String GOOGLE_VERIFYING_PUBLIC_KEYS_JSON =
45 | "{\n"
46 | + " \"keys\": [\n"
47 | + " {\n"
48 | + " \"keyValue\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPYnHwS8uegWAewQtlxizmLFynw"
49 | + "HcxRT1PK07cDA6/C4sXrVI1SzZCUx8U8S0LjMrT6ird/VW7be3Mz6t/srtRQ==\",\n"
50 | + " \"protocolVersion\": \"ECv1\"\n"
51 | + " },\n"
52 | + " {\n"
53 | + " \"keyValue\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM"
54 | + "43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw==\",\n"
55 | + " \"keyExpiration\": \""
56 | + (System.currentTimeMillis() + 24 * 60 * 60 * 1000)
57 | + "\",\n"
58 | + " \"protocolVersion\": \"ECv2\"\n"
59 | + " },\n"
60 | + " {\n"
61 | + " \"keyValue\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENXvYqxD5WayKYhuXQevdGdLA8i"
62 | + "fV4LsRS2uKvFo8wwyiwgQHB9DiKzG6T/P1Fu9Bl7zWy/se5Dy4wk1mJoPuxg==\",\n"
63 | + " \"keyExpiration\": \""
64 | + (System.currentTimeMillis() + 24 * 60 * 60 * 1000)
65 | + "\",\n"
66 | + " \"protocolVersion\": \"ECv2SigningOnly\"\n"
67 | + " }\n"
68 | + " ]\n"
69 | + "}";
70 |
71 | /**
72 | * Sample Google intermediate private signing key.
73 | *
74 | * Corresponds to {@link #GOOGLE_SIGNING_INTERMEDIATE_PUBLIC_KEY_X509_BASE64}.
75 | */
76 | private static final String GOOGLE_SIGNING_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64 =
77 | "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKvEdSS8f0mjTCNKev"
78 | + "aKXIzfNC5b4A104gJWI9TsLIMqhRANCAAT/X7ccFVJt2/6Ps1oCt2AzKhIAz"
79 | + "jfJHJ3Op2DVPGh1LMD3oOPgxzUSIqujHG6dq9Ui93Eacl4VWJPMW/MVHHIL";
80 |
81 | /**
82 | * Sample Google intermediate public signing key.
83 | *
84 | * Corresponds to {@link #GOOGLE_SIGNING_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64}.
85 | */
86 | private static final String GOOGLE_SIGNING_INTERMEDIATE_PUBLIC_KEY_X509_BASE64 =
87 | "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yR"
88 | + "ydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw==";
89 |
90 | /**
91 | * Sample merchant private key.
92 | *
93 | * Corresponds to {@link #MERCHANT_PUBLIC_KEY_BASE64}
94 | *
95 | * Corresponds to {@link #MERCHANT_PRIVATE_KEY_PKCS8_BASE64}
108 | *
109 | * Created with:
110 | *
111 | * Base64 version of the private key encoded in PKCS8 format.
38 | */
39 | private static final String GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64 =
40 | "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKvEdSS8f0mjTCNKev"
41 | + "aKXIzfNC5b4A104gJWI9TsLIMqhRANCAAT/X7ccFVJt2/6Ps1oCt2AzKhIAz"
42 | + "jfJHJ3Op2DVPGh1LMD3oOPgxzUSIqujHG6dq9Ui93Eacl4VWJPMW/MVHHIL";
43 |
44 | /**
45 | * Sample Google intermediate public signing key for the ECv2 protocolVersion.
46 | *
47 | * Base64 version of the public key encoded in ASN.1 type SubjectPublicKeyInfo defined in the
48 | * X.509 standard.
49 | */
50 | private static final String GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64 =
51 | "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yR"
52 | + "ydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw==";
53 |
54 | @Test
55 | public void shouldProduceSenderIntermediateCertJson() throws Exception {
56 | String encoded =
57 | new SenderIntermediateCertFactory.Builder()
58 | .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
59 | .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
60 | .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64)
61 | .expiration(123456)
62 | .build()
63 | .create();
64 |
65 | JsonObject decodedSignedIntermediateSigningKey =
66 | JsonParser.parseString(encoded).getAsJsonObject();
67 | assertTrue(decodedSignedIntermediateSigningKey.get("signedKey").isJsonPrimitive());
68 | assertTrue(decodedSignedIntermediateSigningKey.get("signatures").isJsonArray());
69 | assertEquals(2, decodedSignedIntermediateSigningKey.size());
70 |
71 | JsonObject signedKey =
72 | JsonParser.parseString(decodedSignedIntermediateSigningKey.get("signedKey").getAsString())
73 | .getAsJsonObject();
74 | assertTrue(signedKey.get("keyValue").getAsJsonPrimitive().isString());
75 | assertTrue(signedKey.get("keyExpiration").getAsJsonPrimitive().isString());
76 | assertEquals(2, signedKey.size());
77 | assertEquals(
78 | GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64,
79 | signedKey.get("keyValue").getAsString());
80 | assertEquals("123456", signedKey.get("keyExpiration").getAsString());
81 | assertEquals(1, decodedSignedIntermediateSigningKey.get("signatures").getAsJsonArray().size());
82 | assertFalse(
83 | decodedSignedIntermediateSigningKey
84 | .get("signatures")
85 | .getAsJsonArray()
86 | .get(0)
87 | .getAsString()
88 | .isEmpty());
89 | }
90 |
91 | @Test
92 | public void shouldThrowIfExpirationNotSet() throws Exception {
93 | try {
94 | new SenderIntermediateCertFactory.Builder()
95 | .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
96 | .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
97 | .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64)
98 | // no expiration
99 | .build();
100 | fail("Should have thrown!");
101 | } catch (IllegalArgumentException expected) {
102 | assertEquals("must set expiration using Builder.expiration", expected.getMessage());
103 | }
104 | }
105 |
106 | @Test
107 | public void shouldThrowIfExpirationIsNegative() throws Exception {
108 | try {
109 | new SenderIntermediateCertFactory.Builder()
110 | .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
111 | .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
112 | .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64)
113 | .expiration(-1)
114 | .build();
115 | fail("Should have thrown!");
116 | } catch (IllegalArgumentException expected) {
117 | assertEquals("invalid negative expiration", expected.getMessage());
118 | }
119 | }
120 |
121 | @Test
122 | public void shouldThrowIfNoSenderSigningKeyAdded() throws Exception {
123 | try {
124 | new SenderIntermediateCertFactory.Builder()
125 | .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
126 | .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
127 | // no call to addSenderSigningKey
128 | .expiration(System.currentTimeMillis() + 24 * 60 * 60 * 1000)
129 | .build();
130 | fail("Should have thrown!");
131 | } catch (IllegalArgumentException expected) {
132 | assertEquals(
133 | "must add at least one sender's signing key using Builder.addSenderSigningKey",
134 | expected.getMessage());
135 | }
136 | }
137 |
138 | @Test
139 | public void shouldSupportECV2SigningOnly() throws Exception {
140 | Object unused = new SenderIntermediateCertFactory.Builder()
141 | .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
142 | .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
143 | .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64)
144 | .expiration(123456)
145 | .build();
146 | }
147 |
148 | @Test
149 | public void shouldThrowIfInvalidProtocolVersionSet() throws Exception {
150 | try {
151 | Object unused = new SenderIntermediateCertFactory.Builder()
152 | .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1)
153 | .build();
154 | fail("Should have thrown!");
155 | } catch (IllegalArgumentException expected) {
156 | assertEquals("invalid version: ECv1", expected.getMessage());
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/rewardedads/BUILD.bazel:
--------------------------------------------------------------------------------
1 | load("//tools:gen_maven_jar_rules.bzl", "gen_maven_jar_rules")
2 |
3 | package(default_visibility = ["//visibility:public"])
4 |
5 | licenses(["notice"])
6 |
7 | gen_maven_jar_rules(
8 | name = "maven",
9 | doctitle = "Tink Cryptography API for Google Mobile Rewarded Video Ads SSV",
10 | manifest_lines = [
11 | "Automatic-Module-Name: com.google.crypto.tink.apps.rewardedads",
12 | ],
13 | root_packages = ["com.google.crypto.tink.apps.rewardedads"],
14 | deps = [
15 | "//rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads:keys_downloader",
16 | "//rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads:rewarded_ads_verifier",
17 | ],
18 | )
19 |
--------------------------------------------------------------------------------
/rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads/BUILD.bazel:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | licenses(["notice"])
4 |
5 | java_library(
6 | name = "rewarded_ads_verifier",
7 | srcs = ["RewardedAdsVerifier.java"],
8 | deps = [
9 | ":keys_downloader",
10 | "@maven//:com_google_code_gson_gson",
11 | "@maven//:com_google_crypto_tink_tink",
12 | "@maven//:com_google_errorprone_error_prone_annotations",
13 | "@maven//:com_google_http_client_google_http_client",
14 | ],
15 | )
16 |
17 | java_library(
18 | name = "keys_downloader",
19 | srcs = ["KeysDownloader.java"],
20 | deps = [
21 | "@maven//:com_google_code_findbugs_jsr305",
22 | "@maven//:com_google_errorprone_error_prone_annotations",
23 | "@maven//:com_google_http_client_google_http_client",
24 | ],
25 | )
26 |
--------------------------------------------------------------------------------
/rewardedads/src/test/BUILD.bazel:
--------------------------------------------------------------------------------
1 | load("//tools:gen_java_test_rules.bzl", "gen_java_test_rules")
2 |
3 | package(default_visibility = ["//visibility:public"])
4 |
5 | licenses(["notice"])
6 |
7 | # Tests
8 |
9 | java_library(
10 | name = "generator_test",
11 | testonly = 1,
12 | srcs = glob([
13 | "**/*.java",
14 | ]),
15 | deps = [
16 | "//rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads:keys_downloader",
17 | "//rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads:rewarded_ads_verifier",
18 | "@maven//:com_google_code_gson_gson",
19 | "@maven//:com_google_crypto_tink_tink",
20 | "@maven//:com_google_errorprone_error_prone_annotations",
21 | "@maven//:com_google_http_client_google_http_client",
22 | "@maven//:junit_junit",
23 | ],
24 | )
25 |
26 | gen_java_test_rules(
27 | test_files = glob([
28 | "**/*Test.java",
29 | ]),
30 | deps = [
31 | ":generator_test",
32 | ],
33 | )
34 |
--------------------------------------------------------------------------------
/tools/BUILD.bazel:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | licenses(["notice"])
4 |
5 | java_binary(
6 | name = "jarjar",
7 | main_class = "org.pantsbuild.jarjar.Main",
8 | visibility = ["//visibility:public"],
9 | runtime_deps = [
10 | "@maven//:org_ow2_asm_asm",
11 | "@maven//:org_ow2_asm_asm_commons",
12 | "@maven//:org_pantsbuild_jarjar",
13 | ],
14 | )
15 |
--------------------------------------------------------------------------------
/tools/gen_java_test_rules.bzl:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Google Inc. All Rights Reserved.
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 | """Generate Java test rules from given test_files.
17 |
18 | Instead of having to create one test rule per test in the BUILD file, this rule
19 | provides a handy way to create a bunch of test rules for the specified test
20 | files.
21 |
22 | """
23 |
24 | load("@rules_java//java:defs.bzl", "java_test")
25 |
26 | def gen_java_test_rules(
27 | test_files,
28 | deps,
29 | data = [],
30 | exclude_tests = [],
31 | default_test_size = "small",
32 | small_tests = [],
33 | medium_tests = [],
34 | large_tests = [],
35 | enormous_tests = [],
36 | flaky_tests = [],
37 | manual_tests = [],
38 | notsan_tests = [],
39 | no_rbe_tests = [],
40 | resources = [],
41 | tags = [],
42 | prefix = "",
43 | jvm_flags = [],
44 | args = [],
45 | visibility = None,
46 | shard_count = 1):
47 | for test in _get_test_names(test_files):
48 | if test in exclude_tests:
49 | continue
50 | test_size = default_test_size
51 | if test in small_tests:
52 | test_size = "small"
53 | if test in medium_tests:
54 | test_size = "medium"
55 | if test in large_tests:
56 | test_size = "large"
57 | if test in enormous_tests:
58 | test_size = "enormous"
59 | manual = []
60 | if test in manual_tests:
61 | manual = ["manual"]
62 | notsan = []
63 | if test in notsan_tests:
64 | notsan = ["notsan"]
65 | no_rbe = []
66 | if test in no_rbe_tests:
67 | no_rbe = ["no_rbe"]
68 | flaky = 0
69 | if (test in flaky_tests) or ("flaky" in tags):
70 | flaky = 1
71 | java_class = _package_from_path(
72 | native.package_name() + "/" + _strip_right(test, ".java"),
73 | )
74 | java_test(
75 | name = prefix + test,
76 | runtime_deps = deps,
77 | data = data,
78 | resources = resources,
79 | size = test_size,
80 | jvm_flags = jvm_flags,
81 | args = args,
82 | flaky = flaky,
83 | tags = tags + manual + notsan + no_rbe,
84 | test_class = java_class,
85 | visibility = visibility,
86 | shard_count = shard_count,
87 | )
88 |
89 | def _get_test_names(test_files):
90 | test_names = []
91 | for test_file in test_files:
92 | if not test_file.endswith("Test.java"):
93 | continue
94 | test_names += [test_file[:-5]]
95 | return test_names
96 |
97 | def _package_from_path(package_path, src_impls = None):
98 | src_impls = src_impls or ["src/test/java/", "javatests/", "java/"]
99 | for src_impl in src_impls:
100 | if not src_impl.endswith("/"):
101 | src_impl += "/"
102 | index = _index_of_end(package_path, src_impl)
103 | if index >= 0:
104 | package_path = package_path[index:]
105 | break
106 | return package_path.replace("/", ".")
107 |
108 | def _strip_right(s, suffix):
109 | if s.endswith(suffix):
110 | return s[0:len(s) - len(suffix)]
111 | else:
112 | return s
113 |
114 | def _index_of_end(s, part):
115 | index = s.find(part)
116 | if index >= 0:
117 | return index + len(part)
118 | return -1
119 |
--------------------------------------------------------------------------------
/tools/gen_maven_jar_rules.bzl:
--------------------------------------------------------------------------------
1 | # Copyright 2020 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 | """ Definition of gen_maven_jar_rules. """
16 |
17 | load("//tools:jar_jar.bzl", "jar_jar")
18 | load("//tools:java_single_jar.bzl", "java_single_jar")
19 | load("//tools:javadoc.bzl", "javadoc_library")
20 |
21 | _EXTERNAL_JAVADOC_LINKS = [
22 | "https://docs.oracle.com/javase/8/docs/api/",
23 | "https://developer.android.com/reference/",
24 | ]
25 |
26 | _TINK_PACKAGES = [
27 | "com.google.crypto.tink",
28 | ]
29 |
30 | def gen_maven_jar_rules(
31 | name,
32 | deps = [],
33 | resources = [],
34 | root_packages = _TINK_PACKAGES,
35 | shaded_packages = [],
36 | shading_rules = "",
37 | exclude_packages = [],
38 | doctitle = "",
39 | android_api_level = 23,
40 | bottom_text = "",
41 | external_javadoc_links = _EXTERNAL_JAVADOC_LINKS,
42 | manifest_lines = []):
43 | """
44 | Generates rules that generate Maven jars for a given package.
45 |
46 | Args:
47 | name: Given a name, this function generates 3 rules: a compiled package
48 | name.jar, a source package name-src.jar and a Javadoc package
49 | name-javadoc.jar.
50 | deps: A combination of the deps of java_single_jar and javadoc_library
51 | resources: A list of resource files. Files must be stored in
52 | src/main/resources. Mapping rules: src/main/resources/a/b/c.txt will be
53 | copied to a/b/c.txt in the output jar.
54 | root_packages: See javadoc_library
55 | shaded_packages: These packages will be shaded, according to the rules
56 | specified in shading_rules.
57 | shading_rules: The shading rules, must specified when shaded_packages is present.
58 | Rules file format can be found at https://github.com/bazelbuild/bazel/blob/master/third_party/jarjar/java/com/tonicsystems/jarjar/help.txt.
59 | exclude_packages: See javadoc_library
60 | doctitle: See javadoc_library
61 | android_api_level: See javadoc_library
62 | bottom_text: See javadoc_library
63 | external_javadoc_links: See javadoc_library
64 | manifest_lines: lines to put in the output manifest file (manifest
65 | files in the input jars are ignored)
66 | """
67 |
68 | if shaded_packages:
69 | unshaded_jar = name + "-unshaded"
70 | java_single_jar(
71 | name = unshaded_jar,
72 | deps = deps,
73 | resources = resources,
74 | root_packages = root_packages + shaded_packages,
75 | manifest_lines = manifest_lines,
76 | )
77 | jar_jar(
78 | name = name,
79 | input_jar = unshaded_jar,
80 | rules = shading_rules,
81 | )
82 | else:
83 | java_single_jar(
84 | name = name,
85 | deps = deps,
86 | resources = resources,
87 | root_packages = root_packages,
88 | manifest_lines = manifest_lines,
89 | )
90 |
91 | source_jar_name = name + "-src"
92 | java_single_jar(
93 | name = source_jar_name,
94 | deps = deps,
95 | root_packages = root_packages,
96 | source_jar = True,
97 | )
98 |
99 | javadoc_name = name + "-javadoc"
100 | javadoc_library(
101 | name = javadoc_name,
102 | deps = deps,
103 | root_packages = root_packages,
104 | srcs = [":%s" % source_jar_name],
105 | doctitle = doctitle,
106 | exclude_packages = exclude_packages,
107 | android_api_level = android_api_level,
108 | bottom_text = bottom_text,
109 | external_javadoc_links = external_javadoc_links,
110 | )
111 |
--------------------------------------------------------------------------------
/tools/jar_jar.bzl:
--------------------------------------------------------------------------------
1 | # Copyright 2020 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 | """starlark rules for jarjar. See https://github.com/pantsbuild/jarjar
15 | """
16 |
17 | load("@rules_java//java:defs.bzl", "JavaInfo")
18 |
19 | def _jar_jar_impl(ctx):
20 | ctx.actions.run(
21 | inputs = [ctx.file.rules, ctx.file.input_jar],
22 | outputs = [ctx.outputs.jar],
23 | executable = ctx.executable._jarjar,
24 | progress_message = "jarjar %s" % ctx.label,
25 | arguments = ["process", ctx.file.rules.path, ctx.file.input_jar.path, ctx.outputs.jar.path],
26 | )
27 |
28 | return [
29 | JavaInfo(
30 | output_jar = ctx.outputs.jar,
31 | compile_jar = ctx.outputs.jar,
32 | ),
33 | DefaultInfo(files = depset([ctx.outputs.jar])),
34 | ]
35 |
36 | jar_jar = rule(
37 | implementation = _jar_jar_impl,
38 | attrs = {
39 | "input_jar": attr.label(allow_single_file = True),
40 | "rules": attr.label(allow_single_file = True),
41 | "_jarjar": attr.label(executable = True, cfg = "exec", default = Label("//tools:jarjar")),
42 | },
43 | outputs = {
44 | "jar": "%{name}.jar",
45 | },
46 | provides = [JavaInfo],
47 | )
48 |
--------------------------------------------------------------------------------
/tools/java_single_jar.bzl:
--------------------------------------------------------------------------------
1 | # Copyright 2020 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 | """ Definition of java_single_jar. """
16 |
17 | load("@rules_java//java:defs.bzl", "JavaInfo")
18 |
19 | def _check_non_empty(value, name):
20 | if not value:
21 | fail("%s must be non-empty" % name)
22 |
23 | def _java_single_jar(ctx):
24 | _check_non_empty(ctx.attr.root_packages, "root_packages")
25 |
26 | inputs = depset()
27 | if ctx.attr.source_jar:
28 | inputs = depset(transitive = [dep[JavaInfo].transitive_source_jars for dep in ctx.attr.deps])
29 | else:
30 | inputs = depset(transitive = [dep[JavaInfo].transitive_runtime_jars for dep in ctx.attr.deps])
31 |
32 | args = ctx.actions.args()
33 | args.add_all("--sources", inputs)
34 | args.use_param_file(
35 | "@%s",
36 | use_always = True,
37 | )
38 | args.set_param_file_format("multiline")
39 | args.add("--output", ctx.outputs.jar)
40 | args.add("--normalize")
41 |
42 | resource_files = depset(
43 | transitive = [resource.files for resource in ctx.attr.resources],
44 | ).to_list()
45 | args.add("--resources")
46 | for resource_file in resource_files:
47 | if not resource_file.path.startswith("src/main/resources"):
48 | fail("resource %s must be stored in src/main/resources/" % resource_file.path)
49 | relative_path = resource_file.path.replace("src/main/resources/", "")
50 |
51 | # Map src/main/resources/a/b/c.txt to a/b/c.txt.
52 | args.add(resource_file.path, format = "%s:" + relative_path)
53 |
54 | # Maybe compress code.
55 | if not ctx.attr.source_jar:
56 | # Deal with limitation of singlejar flags: tool's default behavior is
57 | # "no", but you get that behavior only by absence of compression flags.
58 | if ctx.attr.compress == "preserve":
59 | args.add("--dont_change_compression")
60 | elif ctx.attr.compress == "yes":
61 | args.add("--compression")
62 | elif ctx.attr.compress == "no":
63 | pass
64 | else:
65 | fail("\"compress\" attribute (%s) must be: yes, no, preserve." % ctx.attr.compress)
66 |
67 | # Each package prefix has to be specified in its own --include_prefixes arg.
68 | for p in ctx.attr.root_packages:
69 | args.add("--include_prefixes", p.replace(".", "/"))
70 |
71 | if ctx.attr.exclude_build_data:
72 | args.add("--exclude_build_data")
73 |
74 | args.add_all("--deploy_manifest_lines", ctx.attr.manifest_lines, format_each = "\"%s\"")
75 |
76 | ctx.actions.run(
77 | inputs = inputs.to_list() + resource_files,
78 | outputs = [ctx.outputs.jar],
79 | arguments = [args],
80 | progress_message = "Merging into %s" % ctx.outputs.jar.short_path,
81 | mnemonic = "JavaSingleJar",
82 | executable = ctx.executable._singlejar,
83 | )
84 |
85 | java_single_jar = rule(
86 | attrs = {
87 | "deps": attr.label_list(providers = [JavaInfo]),
88 | "resources": attr.label_list(
89 | providers = [JavaInfo],
90 | allow_files = True,
91 | ),
92 | "_singlejar": attr.label(
93 | default = Label("@bazel_tools//tools/jdk:singlejar"),
94 | cfg = "exec",
95 | allow_single_file = True,
96 | executable = True,
97 | ),
98 | "source_jar": attr.bool(default = False),
99 | "compress": attr.string(default = "preserve"),
100 | "root_packages": attr.string_list(),
101 | "exclude_build_data": attr.bool(default = True),
102 | "manifest_lines": attr.string_list(),
103 | },
104 | outputs = {
105 | "jar": "%{name}.jar",
106 | },
107 | implementation = _java_single_jar,
108 | doc = """
109 | Collects Java dependencies and jar files into a single jar
110 |
111 | Args:
112 | deps: The Java targets (including java_import and java_library) to collect
113 | transitive dependencies from. Both compile-time dependencies (deps,
114 | exports) and runtime dependencies (runtime_deps) are collected.
115 | Resources are also collected. Native cc_library or java_wrap_cc
116 | dependencies are not.
117 | resources: A combination of resource files. Files must be stored in
118 | src/main/resources. Mapping rules: src/main/resources/a/b/c.txt will be
119 | copied to a/b/c.txt in the output jar.
120 | compress: Whether to always deflate ("yes"), always store ("no"), or pass
121 | through unmodified ("preserve"). The default is "preserve", and is the
122 | most efficient option -- no extra work is done to inflate or deflate.
123 | source_jar: Whether to combine only the source jars of input to create a single
124 | output source jar. The compiled code jars of input will be ignored.
125 | root_packages: Java packages to include in generated jar.
126 | exclude_build_data: Whether to omit the build-data.properties file generated
127 | by default.
128 | manifest_lines: lines to put in the output manifest file (manifest
129 | files in the input jars are ignored)
130 |
131 | Outputs:
132 | {name}.jar: A single jar containing all of the input.
133 | """,
134 | )
135 |
--------------------------------------------------------------------------------
/tools/javadoc.bzl:
--------------------------------------------------------------------------------
1 | # Copyright 2020 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 | Generates a Javadoc jar path/to/target/
200 | * {
201 | * // {
202 | * // // A string that identifies this cert
203 | * // "keyValue": "ZXBoZW1lcmFsUHVibGljS2V5"
204 | * // // string (UTC milliseconds since epoch)
205 | * // "expiration": "1520836260646",
206 | * // }
207 | * "signedKey": "... serialized JSON shown in comment above ...",
208 | * "signatures": ["signature1", "signature2", ...],
209 | * }
210 | *
211 | */
212 | public String create() throws GeneralSecurityException {
213 | String signedKey = jsonEncodeSignedKey(intermediateSigningKey, expiration);
214 | byte[] toSignBytes =
215 | PaymentMethodTokenUtil.toLengthValue(
216 | // The order of the parameters matters.
217 | senderId, protocolVersion, signedKey);
218 | ArrayList
96 | * openssl pkcs8 -topk8 -inform PEM -outform PEM -in merchant-key.pem -nocrypt
97 | *
98 | */
99 | private static final String MERCHANT_PRIVATE_KEY_PKCS8_BASE64 =
100 | "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgCPSuFr4iSIaQprjj"
101 | + "chHPyDu2NXFe0vDBoTpPkYaK9dehRANCAATnaFz/vQKuO90pxsINyVNWojabHfbx"
102 | + "9qIJ6uD7Q7ZSxmtyo/Ez3/o2kDT8g0pIdyVIYktCsq65VoQIDWSh2Bdm";
103 |
104 | /**
105 | * Sample merchant public key.
106 | *
107 | *
112 | * openssl ec -in merchant-key.pem -pubout -text -noout 2> /dev/null | grep "pub:" -A5 \
113 | * | xxd -r -p | base64
114 | *
115 | */
116 | private static final String MERCHANT_PUBLIC_KEY_BASE64 =
117 | "BOdoXP+9Aq473SnGwg3JU1aiNpsd9vH2ognq4PtDtlLGa3Kj8TPf+jaQNPyDSkh3JUhiS0KyrrlWhAgNZKHYF2Y=";
118 |
119 | @Test
120 | public void testSealAndUnseal() throws Exception {
121 | // Create a PaymentMethodTokenSender to send sealed messages to a merchant.
122 | PaymentMethodTokenSender sender =
123 | new PaymentMethodTokenSender.Builder()
124 | .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
125 | .senderIntermediateSigningKey(GOOGLE_SIGNING_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64)
126 | .senderIntermediateCert(
127 | new SenderIntermediateCertFactory.Builder()
128 | .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
129 | .addSenderSigningKey(GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64)
130 | .senderIntermediateSigningKey(
131 | GOOGLE_SIGNING_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
132 | .expiration(System.currentTimeMillis() + 24 * 60 * 60 * 1000)
133 | .build()
134 | .create())
135 | .recipientId("[YOUR MERCHANT ID]")
136 | .rawUncompressedRecipientPublicKey(MERCHANT_PUBLIC_KEY_BASE64)
137 | .build();
138 |
139 | String encryptedMessage = sender.seal("plaintext");
140 |
141 | // Create a PaymentMethodTokenRecipient to receive sealed messages.
142 | PaymentMethodTokenRecipient recipient =
143 | new PaymentMethodTokenRecipient.Builder()
144 | .protocolVersion("ECv2")
145 | // IMPORTANT: Instead of using "senderVerifyingKeys" to set the verifying public keys
146 | // of the sender, prefer calling:
147 | // .fetchSenderVerifyingKeysWith(GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION)
148 | .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
149 | .recipientId("[YOUR MERCHANT ID]")
150 | .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
151 | .build();
152 |
153 | String decryptedMessage = recipient.unseal(encryptedMessage);
154 |
155 | assertThat(decryptedMessage).isEqualTo("plaintext");
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/GooglePaymentsPublicKeyManagerTest.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
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.apps.paymentmethodtoken;
18 |
19 | import static org.junit.Assert.assertNotSame;
20 | import static org.junit.Assert.assertSame;
21 | import static org.junit.Assert.fail;
22 |
23 | import org.junit.Test;
24 | import org.junit.runner.RunWith;
25 | import org.junit.runners.JUnit4;
26 |
27 | /** Tests for {@link GooglePaymentsPublicKeysManager}. */
28 | @RunWith(JUnit4.class)
29 | public class GooglePaymentsPublicKeyManagerTest {
30 | @Test
31 | public void builderShouldReturnSingletonsWhenMatching() {
32 | assertSame(
33 | GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION,
34 | new GooglePaymentsPublicKeysManager.Builder().build());
35 | assertSame(
36 | GooglePaymentsPublicKeysManager.INSTANCE_TEST,
37 | new GooglePaymentsPublicKeysManager.Builder()
38 | .setKeysUrl(GooglePaymentsPublicKeysManager.KEYS_URL_TEST)
39 | .build());
40 | }
41 |
42 | @Test
43 | public void builderShouldReturnDifferentInstanceWhenNotMatchingSingletons() {
44 | assertNotSame(
45 | GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION,
46 | new GooglePaymentsPublicKeysManager.Builder().setKeysUrl("https://abc").build());
47 | assertNotSame(
48 | GooglePaymentsPublicKeysManager.INSTANCE_TEST,
49 | new GooglePaymentsPublicKeysManager.Builder().setKeysUrl("https://abc").build());
50 | }
51 |
52 | @Test
53 | public void builderShouldThrowIllegalArgumentExceptionWhenUrlIsNotHttps() {
54 | try {
55 | new GooglePaymentsPublicKeysManager.Builder().setKeysUrl("http://abc").build();
56 | fail("Expected IllegalArgumentException");
57 | } catch (IllegalArgumentException ex) {
58 | // expected.
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodJsonEncodingTest.java:
--------------------------------------------------------------------------------
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.apps.paymentmethodtoken;
18 |
19 | import static java.nio.charset.StandardCharsets.UTF_8;
20 | import static org.junit.Assert.assertEquals;
21 |
22 | import java.util.ArrayList;
23 | import org.junit.Test;
24 | import org.junit.runner.RunWith;
25 | import org.junit.runners.JUnit4;
26 |
27 | /** Tests for the exact Json-Encoding produced.
28 | *
29 | * These tests test implementation details. Do not depend on the this. For example, the particular
30 | * ordering of the elements or the particular character escaping used may change in the future.
31 | * */
32 | @RunWith(JUnit4.class)
33 | public final class PaymentMethodJsonEncodingTest {
34 |
35 | @Test
36 | public void testExactOutputOfJsonEncodeCiphertext() throws Exception {
37 | byte[] ciphertext = "CiPhErTeXt".getBytes(UTF_8);
38 | byte[] tag = "taaag".getBytes(UTF_8);
39 | byte[] ephemeralPublicKey = "ephemeral Public Key".getBytes(UTF_8);
40 |
41 | String jsonEncodedCiphertext =
42 | PaymentMethodTokenHybridEncrypt.jsonEncodeCiphertext(ciphertext, tag, ephemeralPublicKey);
43 |
44 | // JSONObject uses a HashMap, where the ordering is not defined. The ordering is however
45 | // deterministic. And for jsonEncodeCiphertext, the order happens to be first "encryptedMessage"
46 | // then "ephemeralPublicKey", and finally "tag". Also, JSONObject uses HTML-safe encoding.
47 | assertEquals(
48 | "{\"encryptedMessage\":\"Q2lQaEVyVGVYdA\\u003d\\u003d\",\"ephemeralPublicKey\":"
49 | + "\"ZXBoZW1lcmFsIFB1YmxpYyBLZXk\\u003d\",\"tag\":\"dGFhYWc\\u003d\"}",
50 | jsonEncodedCiphertext);
51 | }
52 |
53 | @Test
54 | public void testExactOutputOfJsonEncodeSignedMessage() throws Exception {
55 | String senderIntermediateCert =
56 | "{\"signedKey\":\"{\\\"keyValue\\\":\\\"abcde\\\\u003d\\\\u003d\\\",\\\"keyExpiration\\\""
57 | + ":\\\"1615299372858\\\"}\",\"signatures\":[\"fghijkl\\u003d\"]}";
58 | String version = "ECv1";
59 | String message =
60 | "{\"encryptedMessage\":\"Q2lQaEVyVGVYdA\\u003d\\u003d\",\"ephemeralPublicKey\":\"ZXBoZW1l"
61 | + "cmFsIFB1YmxpYyBLZXk\\u003d\",\"tag\":\"dGFhYWc\\u003d\"}";
62 | byte[] signature = "the signature".getBytes(UTF_8);
63 |
64 | String jsonEncodedSignedMessage =
65 | PaymentMethodTokenSender.jsonEncodeSignedMessage(
66 | message, version, signature, senderIntermediateCert);
67 |
68 | String expected =
69 | "{\"signature\":\"dGhlIHNpZ25hdHVyZQ\\u003d\\u003d\",\"intermediateSigningKey\":{\"signe"
70 | + "dKey\":\"{\\\"keyValue\\\":\\\"abcde\\\\u003d\\\\u003d\\\",\\\"keyExpiration\\\":"
71 | + "\\\"1615299372858\\\"}\",\"signatures\":[\"fghijkl\\u003d\"]},\"protocolVersion\""
72 | + ":\"ECv1\",\"signedMessage\":\"{\\\"encryptedMessage\\\":\\\"Q2lQaEVyVGVYdA\\\\u00"
73 | + "3d\\\\u003d\\\",\\\"ephemeralPublicKey\\\":\\\"ZXBoZW1lcmFsIFB1YmxpYyBLZXk\\\\u00"
74 | + "3d\\\",\\\"tag\\\":\\\"dGFhYWc\\\\u003d\\\"}\"}";
75 | assertEquals(expected, jsonEncodedSignedMessage);
76 |
77 | String expected2 =
78 | "{\"signature\":\"dGhlIHNpZ25hdHVyZQ\\u003d\\u003d\",\"protocolVersion\":\"ECv1\",\"sign"
79 | + "edMessage\":\"{\\\"encryptedMessage\\\":\\\"Q2lQaEVyVGVYdA\\\\u003d\\\\u003d\\\","
80 | + "\\\"ephemeralPublicKey\\\":\\\"ZXBoZW1lcmFsIFB1YmxpYyBLZXk\\\\u003d\\\",\\\"tag\\"
81 | + "\":\\\"dGFhYWc\\\\u003d\\\"}\"}";
82 |
83 | String jsonEncodedSignedMessage2 =
84 | PaymentMethodTokenSender.jsonEncodeSignedMessage(message, version, signature, null);
85 | assertEquals(expected2, jsonEncodedSignedMessage2);
86 | }
87 |
88 | @Test
89 | public void testExactOutputOfJsonEncodedSignedKey() throws Exception {
90 | String intermediateSigningKey =
91 | "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSA"
92 | + "M43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw==";
93 | long expiration = 1520836260646L;
94 | assertEquals(
95 | "{\"keyValue\":\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yRydzqdg1"
96 | + "TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw\\u003d\\u003d\",\"keyExpiration\""
97 | + ":\"1520836260646\"}",
98 | SenderIntermediateCertFactory.jsonEncodeSignedKey(intermediateSigningKey, expiration));
99 | }
100 |
101 | @Test
102 | public void testExactOutputOfJsonEncodeCertificate() throws Exception {
103 | String intermediateSigningKey =
104 | "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSA"
105 | + "M43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw==";
106 | long expiration = 1520836260646L;
107 | String signedKey =
108 | SenderIntermediateCertFactory.jsonEncodeSignedKey(intermediateSigningKey, expiration);
109 | ArrayList