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