001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2021, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.jose.crypto.impl;
019
020import com.google.crypto.tink.subtle.XChaCha20Poly1305;
021import com.nimbusds.jose.JOSEException;
022import com.nimbusds.jose.util.ByteUtils;
023import com.nimbusds.jose.util.Container;
024import net.jcip.annotations.ThreadSafe;
025
026import javax.crypto.*;
027import java.security.*;
028
029/**
030 * This class defines the XChaCha20 stream cipher as well as the use of the Poly1305 authenticator.
031 *
032 * The eXtended-nonce ChaCha cipher construction (XChaCha) allows for
033 * ChaCha-based cipher suites to accept a 192-bit nonce with similar guarantees
034 * to the original construction, except with a much lower probability of nonce
035 * misuse occurring.
036 *
037 * <p>This class is thread-safe.
038 *
039 * @see <a href="https://datatracker.ietf.org/doc/html/draft-arciszewski-xchacha-03">XChaCha:
040 * eXtended-nonce ChaCha and AEAD_XChaCha20_Poly1305</a>
041 *
042 *
043 * @author Alexander Martynov
044 * @version 2021-08-04
045 */
046@ThreadSafe
047public class XC20P {
048
049    /**
050     * The standard authentication tag length (128 bits).
051     */
052    public static final int AUTH_TAG_BIT_LENGTH = 128;
053
054    /**
055     * The standard Initialisation Vector (IV) length (192 bits).
056     */
057    public static final int IV_BIT_LENGTH = 192;
058
059
060    /**
061     * Encrypts the specified plain text using XChaCha20_Poly1305.
062     *
063     * @param secretKey   The AES key. Must not be {@code null}.
064     * @param plainText   The plain text. Must not be {@code null}.
065     * @param ivContainer The initialisation vector (IV).
066     *                    This is output parameter. On output, it carries
067     *                    the nonce the cipher actually used.
068     * @param authData    The authenticated data. Must not be {@code null}.
069     *
070     * @return The authenticated cipher text.
071     *
072     * @throws JOSEException If encryption failed.
073     */
074    public static AuthenticatedCipherText encryptAuthenticated(final SecretKey secretKey,
075                                        final Container<byte[]> ivContainer,
076                                        final byte[] plainText,
077                                        final byte[] authData)
078            throws JOSEException {
079
080        final XChaCha20Poly1305 aead;
081
082        try {
083            aead = new XChaCha20Poly1305(secretKey.getEncoded());
084
085        } catch (InvalidKeyException e) {
086            throw new JOSEException("Invalid XChaCha20Poly1305 key: " + e.getMessage(), e);
087        }
088
089        final byte[] cipherOutput;
090
091        try {
092            cipherOutput = aead.encrypt(plainText, authData);
093
094        } catch (GeneralSecurityException e) {
095            throw new JOSEException("Couldn't encrypt with XChaCha20Poly1305: " + e.getMessage(), e);
096        }
097
098        final int tagPos = cipherOutput.length - ByteUtils.byteLength(AUTH_TAG_BIT_LENGTH);
099        final int cipherTextPos = ByteUtils.byteLength(IV_BIT_LENGTH);
100
101        byte[] iv = ByteUtils.subArray(cipherOutput, 0, cipherTextPos);
102        byte[] cipherText = ByteUtils.subArray(cipherOutput, cipherTextPos, tagPos - cipherTextPos);
103        byte[] authTag = ByteUtils.subArray(cipherOutput, tagPos, ByteUtils.byteLength(AUTH_TAG_BIT_LENGTH));
104
105        // set nonce
106        ivContainer.set(iv);
107
108        return new AuthenticatedCipherText(cipherText, authTag);
109    }
110
111
112    /**
113     * Decrypts the specified cipher text using XChaCha20_Poly1305.
114     *
115     * @param secretKey  The AES key. Must not be {@code null}.
116     * @param iv         The initialisation vector (IV). Must not be
117     *                   {@code null}.
118     * @param cipherText The cipher text. Must not be {@code null}.
119     * @param authData   The authenticated data. Must not be {@code null}.
120     * @param authTag    The authentication tag. Must not be {@code null}.
121     *
122     * @return The decrypted plain text.
123     *
124     * @throws JOSEException If decryption failed.
125     */
126    public static byte[] decryptAuthenticated(final SecretKey secretKey,
127                                 final byte[] iv,
128                                 final byte[] cipherText,
129                                 final byte[] authData,
130                                 final byte[] authTag)
131            throws JOSEException {
132
133        final XChaCha20Poly1305 aead;
134
135        try {
136            aead = new XChaCha20Poly1305(secretKey.getEncoded());
137
138        } catch (InvalidKeyException e) {
139            throw new JOSEException("Invalid XChaCha20Poly1305 key: " + e.getMessage(), e);
140        }
141
142        final byte[] cipherInput = ByteUtils.concat(iv, cipherText, authTag);
143
144        try {
145            return aead.decrypt(cipherInput, authData);
146
147        } catch (GeneralSecurityException e) {
148
149            throw new JOSEException("XChaCha20Poly1305decryption failed: " + e.getMessage(), e);
150        }
151    }
152}