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
020
021import java.security.GeneralSecurityException;
022import javax.crypto.SecretKey;
023
024import com.google.crypto.tink.subtle.XChaCha20Poly1305;
025import net.jcip.annotations.ThreadSafe;
026
027import com.nimbusds.jose.JOSEException;
028import com.nimbusds.jose.util.ByteUtils;
029import com.nimbusds.jose.util.Container;
030
031
032/**
033 * This class defines the XChaCha20 stream cipher as well as the use of the
034 * Poly1305 authenticator.
035 *
036 * <p>The eXtended-nonce ChaCha cipher construction (XChaCha) allows for
037 * ChaCha-based cipher suites to accept a 192-bit nonce with similar guarantees
038 * to the original construction, except with a much lower probability of nonce
039 * misuse occurring.
040 *
041 * <p>This class is thread-safe.
042 *
043 * @author Alexander Martynov
044 * @version 2022-02-24
045 * @see <a href="https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha-03">XChaCha:
046 * eXtended-nonce ChaCha and AEAD_XChaCha20_Poly1305</a>
047 */
048@ThreadSafe
049public class XC20P {
050        
051        
052        /**
053         * The standard authentication tag length (128 bits).
054         */
055        public static final int AUTH_TAG_BIT_LENGTH = 128;
056        
057        
058        /**
059         * The standard Initialisation Vector (IV) length (192 bits).
060         */
061        public static final int IV_BIT_LENGTH = 192;
062        
063        
064        /**
065         * Encrypts the specified plain text using XChaCha20_Poly1305.
066         *
067         * @param secretKey   The AES key. Must not be {@code null}.
068         * @param plainText   The plain text. Must not be {@code null}.
069         * @param ivContainer The initialisation vector (IV).
070         *                    This is output parameter. On output, it carries
071         *                    the nonce the cipher actually used.
072         * @param authData    The authenticated data. Must not be {@code null}.
073         *
074         * @return The authenticated cipher text.
075         *
076         * @throws JOSEException If encryption failed.
077         */
078        public static AuthenticatedCipherText encryptAuthenticated(final SecretKey secretKey,
079                                                                   final Container<byte[]> ivContainer,
080                                                                   final byte[] plainText,
081                                                                   final byte[] authData)
082                throws JOSEException {
083                
084                final XChaCha20Poly1305 aead;
085                
086                try {
087                        aead = new XChaCha20Poly1305(secretKey.getEncoded());
088                        
089                } catch (GeneralSecurityException e) {
090                        throw new JOSEException("Invalid XChaCha20Poly1305 key: " + e.getMessage(), e);
091                }
092                
093                final byte[] cipherOutput;
094                
095                try {
096                        cipherOutput = aead.encrypt(plainText, authData);
097                        
098                } catch (GeneralSecurityException e) {
099                        throw new JOSEException("Couldn't encrypt with XChaCha20Poly1305: " + e.getMessage(), e);
100                }
101                
102                final int tagPos = cipherOutput.length - ByteUtils.byteLength(AUTH_TAG_BIT_LENGTH);
103                final int cipherTextPos = ByteUtils.byteLength(IV_BIT_LENGTH);
104                
105                byte[] iv = ByteUtils.subArray(cipherOutput, 0, cipherTextPos);
106                byte[] cipherText = ByteUtils.subArray(cipherOutput, cipherTextPos, tagPos - cipherTextPos);
107                byte[] authTag = ByteUtils.subArray(cipherOutput, tagPos, ByteUtils.byteLength(AUTH_TAG_BIT_LENGTH));
108                
109                // set nonce
110                ivContainer.set(iv);
111                
112                return new AuthenticatedCipherText(cipherText, authTag);
113        }
114        
115        
116        /**
117         * Decrypts the specified cipher text using XChaCha20_Poly1305.
118         *
119         * @param secretKey  The AES key. Must not be {@code null}.
120         * @param iv         The initialisation vector (IV). Must not be
121         *                   {@code null}.
122         * @param cipherText The cipher text. Must not be {@code null}.
123         * @param authData   The authenticated data. Must not be {@code null}.
124         * @param authTag    The authentication tag. Must not be {@code null}.
125         *
126         * @return The decrypted plain text.
127         *
128         * @throws JOSEException If decryption failed.
129         */
130        public static byte[] decryptAuthenticated(final SecretKey secretKey,
131                                                  final byte[] iv,
132                                                  final byte[] cipherText,
133                                                  final byte[] authData,
134                                                  final byte[] authTag)
135                throws JOSEException {
136                
137                final XChaCha20Poly1305 aead;
138                
139                try {
140                        aead = new XChaCha20Poly1305(secretKey.getEncoded());
141                        
142                } catch (GeneralSecurityException e) {
143                        throw new JOSEException("Invalid XChaCha20Poly1305 key: " + e.getMessage(), e);
144                }
145                
146                final byte[] cipherInput = ByteUtils.concat(iv, cipherText, authTag);
147                
148                try {
149                        return aead.decrypt(cipherInput, authData);
150                        
151                } catch (GeneralSecurityException e) {
152                        
153                        throw new JOSEException("XChaCha20Poly1305 decryption failed: " + e.getMessage(), e);
154                }
155        }
156}