• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net.ssl;
18 
19 import android.annotation.FlaggedApi;
20 import android.annotation.SystemApi;
21 
22 import libcore.util.NonNull;
23 import libcore.util.Nullable;
24 
25 import java.security.InvalidParameterException;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.Map;
29 
30 /**
31  * An class representing a PAKE (Password Authenticated Key Exchange)
32  * option for TLS connections.
33  *
34  * <p>Instances of this class are immutable. Use the {@link Builder} to create
35  * instances.</p>
36  *
37  * @hide
38  */
39 @SystemApi
40 @FlaggedApi(com.android.org.conscrypt.flags.Flags.FLAG_SPAKE2PLUS_API)
41 public final class PakeOption {
42     private static final int MAX_HANDSHAKE_LIMIT = 24;
43 
44     /**
45      * The algorithm of the PAKE algorithm.
46      */
47     private final String algorithm; // For now "SPAKE2PLUS_PRERELEASE" is suported
48 
49     /**
50      * A map containing the message components for the PAKE exchange.
51      *
52      * <p>The keys are strings representing the component algorithms (e.g., "password",
53      * "w0", "w1"). The values are byte arrays containing the component data.</p>
54      */
55     private final Map<String, byte[]> messageComponents;
56 
PakeOption(String algorithm, Map<String, byte[]> messageComponents)57     private PakeOption(String algorithm, Map<String, byte[]> messageComponents) {
58         this.algorithm = algorithm;
59         this.messageComponents = Collections.unmodifiableMap(new HashMap<>(messageComponents));
60     }
61 
62     /**
63      * Returns the algorithm of the PAKE algorithm.
64      *
65      * @return The algorithm of the PAKE algorithm.
66      */
getAlgorithm()67     public @NonNull String getAlgorithm() {
68         return algorithm;
69     }
70 
71     /**
72      * Returns the message component with the given key.
73      *
74      * @param key The algorithm of the component.
75      * @return The component data, or {@code null} if no component with the given
76      *         key exists.
77      */
getMessageComponent(@onNull String key)78     public @Nullable byte[] getMessageComponent(@NonNull String key) {
79         return messageComponents.get(key);
80     }
81 
82     /**
83      * A builder for creating {@link PakeOption} instances.
84      *
85      * @hide
86      */
87     @SystemApi
88     @FlaggedApi(com.android.org.conscrypt.flags.Flags.FLAG_SPAKE2PLUS_API)
89     public static final class Builder {
90         private String algorithm;
91         private Map<String, byte[]> messageComponents = new HashMap<>();
92 
93         /**
94          * Constructor for the builder.
95          *
96          * @param algorithm The algorithm of the PAKE algorithm.
97          * @throws InvalidParameterException If the algorithm is invalid.
98          */
Builder(@onNull String algorithm)99         public Builder(@NonNull String algorithm) {
100             if (algorithm == null || algorithm.isEmpty()) {
101                 throw new InvalidParameterException("Algorithm cannot be null or empty.");
102             }
103             this.algorithm = algorithm;
104         }
105 
106         /**
107          * Adds a message component. For SPAKE2+ password is the only required component. For
108          * SPAKE2+ 'client-handshake-limit' and 'server-handshake-limit' are optional and will be obtained using
109          * the first byte found in the input byte array. It must be an integer between 1 and 24. These limits are used to limit the number of unfinished or
110          * failed handshakes that can be performed using this PAKE option. If not specified, the
111          * default limit is 1. Be aware that higher limits increase the security risk of the
112          * connection since there are more opportunities for brute force attacks.
113          *
114          * @param key The algorithm of the component.
115          * @param value The component data.
116          * @return This builder.
117          * @throws InvalidParameterException If the key is invalid.
118          */
addMessageComponent(@onNull String key, @Nullable byte[] value)119         public @NonNull Builder addMessageComponent(@NonNull String key, @Nullable byte[] value) {
120             if (key == null || key.isEmpty()) {
121                 throw new InvalidParameterException("Key cannot be null or empty.");
122             }
123             messageComponents.put(key, value.clone());
124             return this;
125         }
126 
127         /**
128          * Builds a new {@link PakeOption} instance.
129          *
130          * <p>This method performs validation to ensure that the message components
131          * are consistent with the PAKE algorithm.</p>
132          *
133          * @return A new {@link PakeOption} instance.
134          * @throws InvalidParameterException If the message components are invalid.
135          */
build()136         public @NonNull PakeOption build() {
137             if (messageComponents.isEmpty()) {
138                 throw new InvalidParameterException("Message components cannot be empty.");
139             }
140             if (algorithm.equals("SPAKE2PLUS_PRERELEASE")) {
141                 validateSpake2PlusComponents();
142             }
143 
144             return new PakeOption(algorithm, messageComponents);
145         }
146 
validateSpake2PlusComponents()147         private void validateSpake2PlusComponents() {
148             // For SPAKE2+ password is the only required component.
149             if (!messageComponents.containsKey("password")) {
150                 throw new InvalidParameterException(
151                         "For SPAKE2+, 'password' must be present.");
152             }
153             // If 'client-handshake-limit' or 'server-handshake-limit' are present,
154             // they must be integers between 1 and 24.
155             if (messageComponents.containsKey("client-handshake-limit")) {
156                 int clientHandshakeLimit =
157                         messageComponents
158                                 .get("client-handshake-limit")[0];
159                 if (clientHandshakeLimit < 1 || clientHandshakeLimit > MAX_HANDSHAKE_LIMIT) {
160                     throw new InvalidParameterException(
161                             "For SPAKE2+, 'client-handshake-limit' must be between 1 and 24.");
162                 }
163             }
164             if (messageComponents.containsKey("server-handshake-limit")) {
165                 int serverHandshakeLimit =
166                         messageComponents
167                                 .get("server-handshake-limit")[0];
168                 if (serverHandshakeLimit < 1 || serverHandshakeLimit > MAX_HANDSHAKE_LIMIT) {
169                     throw new InvalidParameterException(
170                             "For SPAKE2+, 'server-handshake-limit' must be between 1 and 24.");
171                 }
172             }
173         }
174     }
175 }
176