aboutsummaryrefslogtreecommitdiff
path: root/src/java/com/android/ike/ikev2/message/IkeMessage.java
blob: 5b63ee7fab14330aa97687a6cb851f366a30da39 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ike.ikev2.message;

import static com.android.ike.ikev2.message.IkePayload.PayloadType;

import android.annotation.IntDef;
import android.util.Pair;

import com.android.ike.ikev2.IkeSessionOptions;
import com.android.ike.ikev2.SaRecord.IkeSaRecord;
import com.android.ike.ikev2.exceptions.IkeException;
import com.android.ike.ikev2.exceptions.InvalidSyntaxException;
import com.android.ike.ikev2.exceptions.UnsupportedCriticalPayloadException;
import com.android.internal.annotations.VisibleForTesting;
import com.android.org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.Provider;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;

/**
 * IkeMessage represents an IKE message.
 *
 * <p>It contains all attributes and provides methods for encoding, decoding, encrypting and
 * decrypting.
 *
 * @see <a href="https://tools.ietf.org/html/rfc7296#section-3">RFC 7296, Internet Key Exchange
 *     Protocol Version 2 (IKEv2)</a>
 */
public final class IkeMessage {

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
        MESSAGE_TYPE_IKE_INIT_RESP,
        MESSAGE_TYPE_IKE_AUTH_RESP,
        MESSAGE_TYPE_DELETE_IKE_REQ,
        MESSAGE_TYPE_DELETE_IKE_RESP,
        MESSAGE_TYPE_REKEY_IKE_REQ,
        MESSAGE_TYPE_REKEY_IKE_RESP,
        MESSAGE_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD,
        MESSAGE_TYPE_INVALID_MAJOR_VERSION,
        MESSAGE_TYPE_INVALID_SYNTAX
    })
    public @interface MessageType {}

    // Message type for decoded IkeMessage.
    public static final int PROCEDURE_TYPE_BASE = 0;

    public static final int MESSAGE_TYPE_IKE_INIT_RESP = PROCEDURE_TYPE_BASE + 1;
    public static final int MESSAGE_TYPE_IKE_AUTH_RESP = PROCEDURE_TYPE_BASE + 2;
    public static final int MESSAGE_TYPE_DELETE_IKE_REQ = PROCEDURE_TYPE_BASE + 3;
    public static final int MESSAGE_TYPE_DELETE_IKE_RESP = PROCEDURE_TYPE_BASE + 4;
    public static final int MESSAGE_TYPE_REKEY_IKE_REQ = PROCEDURE_TYPE_BASE + 5;
    public static final int MESSAGE_TYPE_REKEY_IKE_RESP = PROCEDURE_TYPE_BASE + 6;

    public static final int NOTIFICATION_TYPE_BASE = PROCEDURE_TYPE_BASE + 100;
    public static final int MESSAGE_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD =
            NOTIFICATION_TYPE_BASE + IkeNotifyPayload.NOTIFY_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD;
    public static final int MESSAGE_TYPE_INVALID_MAJOR_VERSION =
            NOTIFICATION_TYPE_BASE + IkeNotifyPayload.NOTIFY_TYPE_INVALID_MAJOR_VERSION;
    public static final int MESSAGE_TYPE_INVALID_SYNTAX =
            NOTIFICATION_TYPE_BASE + IkeNotifyPayload.NOTIFY_TYPE_INVALID_SYNTAX;

    private static IIkeMessageHelper sIkeMessageHelper = new IkeMessageHelper();
    // Currently use Bouncy Castle as crypto security provider
    static final Provider SECURITY_PROVIDER = new BouncyCastleProvider();

    public final IkeHeader ikeHeader;
    public final List<IkePayload> ikePayloadList;
    /**
     * Conctruct an instance of IkeMessage. It is called by decode or for building outbound message.
     *
     * @param header the header of this IKE message
     * @param payloadList the list of decoded IKE payloads in this IKE message
     */
    public IkeMessage(IkeHeader header, List<IkePayload> payloadList) {
        ikeHeader = header;
        ikePayloadList = payloadList;
    }

    /**
     * Get security provider for IKE library
     *
     * <p>Use BouncyCastleProvider as the default security provider.
     *
     * @return the security provider of IKE library.
     */
    public static Provider getSecurityProvider() {
        // TODO: Move this getter out of IKE message package since not only this package uses it.
        return SECURITY_PROVIDER;
    }

    /**
     * Decode unencrypted IKE message body and create an instance of IkeMessage.
     *
     * <p>This method catches all RuntimeException during decoding incoming IKE packet.
     *
     * @param header the IKE header that is decoded but not validated.
     * @param inputPacket the byte array contains the whole IKE message.
     * @return the IkeMessage instance.
     * @throws IkeException if there is any protocol error.
     */
    public static IkeMessage decode(IkeHeader header, byte[] inputPacket) throws IkeException {
        return sIkeMessageHelper.decode(header, inputPacket);
    }

    /**
     * Decrypt and decode encrypted IKE message body and create an instance of IkeMessage.
     *
     * @param ikeSessionOptions IkeSessionOptions that contains cryptographic algorithm set.
     * @param ikeSaRecord ikeSaRecord where this packet is sent on.
     * @param ikeHeader header of IKE packet.
     * @param packet IKE packet as a byte array.
     * @return decoded IKE message.
     * @throws IkeException for decoding errors.
     * @throws GeneralSecurityException if there is any error during integrity check or decryption.
     */
    public static IkeMessage decode(
            IkeSessionOptions ikeSessionOptions,
            IkeSaRecord ikeSaRecord,
            IkeHeader ikeHeader,
            byte[] packet)
            throws IkeException, GeneralSecurityException {
        return sIkeMessageHelper.decode(ikeSessionOptions, ikeSaRecord, ikeHeader, packet);
    }

    private static List<IkePayload> decodePayloadList(
            @PayloadType int firstPayloadType, boolean isResp, byte[] unencryptedPayloads)
            throws IkeException {
        ByteBuffer inputBuffer = ByteBuffer.wrap(unencryptedPayloads);
        int currentPayloadType = firstPayloadType;
        // For supported payload
        List<IkePayload> supportedPayloadList = new LinkedList<>();
        // For unsupported critical payload
        List<Integer> unsupportedCriticalPayloadList = new LinkedList<>();

        while (currentPayloadType != IkePayload.PAYLOAD_TYPE_NO_NEXT) {
            Pair<IkePayload, Integer> pair =
                    IkePayloadFactory.getIkePayload(currentPayloadType, isResp, inputBuffer);
            IkePayload payload = pair.first;

            if (!(payload instanceof IkeUnsupportedPayload)) {
                supportedPayloadList.add(payload);
            } else if (payload.isCritical) {
                unsupportedCriticalPayloadList.add(payload.payloadType);
            }
            // Simply ignore unsupported uncritical payload.

            currentPayloadType = pair.second;
        }

        if (inputBuffer.remaining() > 0) {
            throw new InvalidSyntaxException(
                    "Malformed IKE Payload: Unexpected bytes at the end of packet.");
        }

        if (unsupportedCriticalPayloadList.size() > 0) {
            throw new UnsupportedCriticalPayloadException(unsupportedCriticalPayloadList);
        }
        return supportedPayloadList;
    }

    /**
     * Encode unencrypted IKE message.
     *
     * @return encoded IKE message in byte array.
     */
    public byte[] encode() {
        return sIkeMessageHelper.encode(this);
    }

    /**
     * Encrypt and encode packet.
     *
     * @param ikeSessionOptions IkeSessionOptions that contains cryptographic algorithm set.
     * @param ikeSaRecord ikeSaRecord where this packet is sent on.
     * @return encoded IKE message in byte array.
     */
    public byte[] encode(IkeSessionOptions ikeSessionOptions, IkeSaRecord ikeSaRecord) {
        return sIkeMessageHelper.encode(ikeSessionOptions, ikeSaRecord, this);
    }

    /**
     * Encode all payloads to a byte array.
     *
     * @return byte array contains all encoded payloads
     */
    private byte[] encodePayloads() {
        if (ikePayloadList.isEmpty()) {
            return new byte[0];
        }

        int payloadLengthSum = 0;
        for (IkePayload payload : ikePayloadList) {
            payloadLengthSum += payload.getPayloadLength();
        }

        ByteBuffer byteBuffer = ByteBuffer.allocate(payloadLengthSum);

        for (int i = 0; i < ikePayloadList.size() - 1; i++) {
            ikePayloadList
                    .get(i)
                    .encodeToByteBuffer(ikePayloadList.get(i + 1).payloadType, byteBuffer);
        }
        ikePayloadList
                .get(ikePayloadList.size() - 1)
                .encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_NO_NEXT, byteBuffer);

        return byteBuffer.array();
    }

    /** Package */
    @VisibleForTesting
    byte[] attachEncodedHeader(byte[] encodedIkeBody) {
        ByteBuffer outputBuffer =
                ByteBuffer.allocate(IkeHeader.IKE_HEADER_LENGTH + encodedIkeBody.length);
        ikeHeader.encodeToByteBuffer(outputBuffer, encodedIkeBody.length);
        outputBuffer.put(encodedIkeBody);
        return outputBuffer.array();
    }

    @MessageType
    public int getMessageType() {
        return sIkeMessageHelper.getMessageType(this);
    }

    /**
     * IIkeMessageHelper provides interface for decoding, encoding and processing IKE packet.
     *
     * <p>IkeMessageHelper exists so that the interface is injectable for testing.
     */
    @VisibleForTesting
    public interface IIkeMessageHelper {
        /**
         * Check message type of decoded IKE message.
         *
         * @param ikeMessage IKE message to be checked.
         * @return message type.
         */
        @MessageType
        int getMessageType(IkeMessage ikeMessage);

        /**
         * Encode IKE message.
         *
         * @param ikeMessage message need to be encoded.
         * @return encoded IKE message in byte array.
         */
        byte[] encode(IkeMessage ikeMessage);

        /**
         * Encrypt and encode IKE message.
         *
         * @param ikeSessionOptions ikeSessionOptions that contains cryptographic algorithm set.
         * @param ikeSaRecord ikeSaRecord where this packet is sent on.
         * @param ikeMessage message need to be encoded.
         * @return encoded IKE message in byte array.
         */
        byte[] encode(
                IkeSessionOptions ikeSessionOptions,
                IkeSaRecord ikeSaRecord,
                IkeMessage ikeMessage);

        /**
         * Decode unencrypted packet.
         *
         * @param ikeHeader header of IKE packet.
         * @param packet IKE packet as a byte array.
         * @return decoded IKE message.
         * @throws IkeException for decoding errors.
         */
        IkeMessage decode(IkeHeader ikeHeader, byte[] packet) throws IkeException;

        /**
         * Decrypt and decode packet.
         *
         * @param ikeSessionOptions ikeSessionOptions that contains cryptographic algorithm set.
         * @param ikeSaRecord ikeSaRecord where this packet is sent on.
         * @param ikeHeader header of IKE packet.
         * @param packet IKE packet as a byte array.
         * @return decoded IKE message.
         * @throws IkeException for decoding errors.
         */
        IkeMessage decode(
                IkeSessionOptions ikeSessionOptions,
                IkeSaRecord ikeSaRecord,
                IkeHeader ikeHeader,
                byte[] packet)
                throws IkeException, GeneralSecurityException;
    }

    /** IkeMessageHelper provides methods for decoding, encoding and processing IKE packet. */
    public static final class IkeMessageHelper implements IIkeMessageHelper {
        @Override
        public byte[] encode(IkeMessage ikeMessage) {
            byte[] encodedIkeBody = ikeMessage.encodePayloads();
            return ikeMessage.attachEncodedHeader(encodedIkeBody);
        }

        @Override
        public byte[] encode(
                IkeSessionOptions ikeSessionOptions,
                IkeSaRecord ikeSaRecord,
                IkeMessage ikeMessage) {
            // TODO: Extract crypto attributes and call encrypt()
            return null;
        }

        //TODO: Create and use a container class for crypto algorithms and keys.
        private byte[] encryptAndEncode(
                IkeHeader ikeHeader,
                @PayloadType int firstPayload,
                byte[] unencryptedPayloads,
                Mac integrityMac,
                int checksumLen,
                Cipher encryptCipher,
                SecretKey eKey) {
            IkeSkPayload skPayload =
                    new IkeSkPayload(
                            ikeHeader,
                            firstPayload,
                            unencryptedPayloads,
                            integrityMac,
                            checksumLen,
                            encryptCipher,
                            eKey);

            ByteBuffer outputBuffer =
                    ByteBuffer.allocate(IkeHeader.IKE_HEADER_LENGTH + skPayload.getPayloadLength());
            ikeHeader.encodeToByteBuffer(outputBuffer, skPayload.getPayloadLength());
            skPayload.encodeToByteBuffer(firstPayload, outputBuffer);

            return outputBuffer.array();
        }

        @Override
        public IkeMessage decode(IkeHeader header, byte[] inputPacket) throws IkeException {
            header.checkInboundValidOrThrow(inputPacket.length);

            byte[] unencryptedPayloads =
                    Arrays.copyOfRange(
                            inputPacket, IkeHeader.IKE_HEADER_LENGTH, inputPacket.length);

            try {
                List<IkePayload> supportedPayloadList =
                        decodePayloadList(
                                header.nextPayloadType, header.isResponseMsg, unencryptedPayloads);
                return new IkeMessage(header, supportedPayloadList);
            } catch (NegativeArraySizeException | BufferUnderflowException e) {
                // Invalid length error when parsing payload bodies.
                throw new InvalidSyntaxException("Malformed IKE Payload");
            }
        }

        @Override
        public IkeMessage decode(
                IkeSessionOptions ikeSessionOptions,
                IkeSaRecord ikeSaRecord,
                IkeHeader ikeHeader,
                byte[] packet)
                throws IkeException, GeneralSecurityException {
            // TODO: Extract crypto params and call private decode method.
            return null;
        }

        private IkeMessage decode(
                IkeHeader header,
                byte[] inputPacket,
                Mac integrityMac,
                int checksumLen,
                Cipher decryptCipher,
                SecretKey dKey)
                throws IkeException, GeneralSecurityException {

            header.checkInboundValidOrThrow(inputPacket.length);

            if (header.nextPayloadType != IkePayload.PAYLOAD_TYPE_SK) {
                // TODO: b/123372339 Handle message containing unprotected payloads.
                throw new UnsupportedOperationException("Message contains unprotected payloads");
            }

            try {
                Pair<IkeSkPayload, Integer> pair =
                        IkePayloadFactory.getIkeSkPayload(
                                inputPacket, integrityMac, checksumLen, decryptCipher, dKey);
                IkeSkPayload skPayload = pair.first;
                int firstPayloadType = pair.second;

                List<IkePayload> supportedPayloadList =
                        decodePayloadList(
                                firstPayloadType,
                                header.isResponseMsg,
                                skPayload.getUnencryptedPayloads());

                return new IkeMessage(header, supportedPayloadList);
            } catch (NegativeArraySizeException | BufferUnderflowException e) {
                // Invalid length error when parsing payload bodies.
                throw new InvalidSyntaxException("Malformed IKE Payload");
            }
        }

        @Override
        @MessageType
        public int getMessageType(IkeMessage ikeMessage) {
            // TODO: Implement it.
            return 0;
        }
    }

    /**
     * For setting mocked IIkeMessageHelper for testing
     *
     * @param helper the mocked IIkeMessageHelper
     */
    public static void setIkeMessageHelper(IIkeMessageHelper helper) {
        sIkeMessageHelper = helper;
    }
}