001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2016, 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.io.ByteArrayOutputStream;
022import java.io.IOException;
023import java.security.MessageDigest;
024import java.security.NoSuchAlgorithmException;
025import javax.crypto.SecretKey;
026import javax.crypto.spec.SecretKeySpec;
027
028import com.nimbusds.jose.EncryptionMethod;
029import com.nimbusds.jose.JOSEException;
030import com.nimbusds.jose.util.IntegerUtils;
031import com.nimbusds.jose.util.StandardCharset;
032
033
034/**
035 * Legacy implementation of a Concatenation Key Derivation Function (KDF) for
036 * use by the deprecated {@code A128CBC+HS256} and {@code A256CBC+HS512}
037 * encryption methods. Provides static methods for deriving the Content
038 * Encryption Key (CEK) and the Content Integrity Key (CIK) from a Content
039 * Master Key (CMKs).
040 *
041 * <p>See draft-ietf-jose-json-web-encryption-08, appendices A.4 and A.5.
042 *
043 * <p>See NIST.800-56A.
044 *
045 * @author Vladimir Dzhuvinov
046 * @version 2018-01-04
047 */
048public class LegacyConcatKDF {
049
050
051        /**
052         * The four byte array (32-byte) representation of 1.
053         */
054        private static final byte[] ONE_BYTES = { (byte)0, (byte)0, (byte)0,  (byte)1 };
055
056
057        /**
058         * The four byte array (32-bit) representation of 0.
059         */
060        private static final byte[] ZERO_BYTES = { (byte)0, (byte)0, (byte)0,  (byte)0 };
061
062
063        /**
064         * The byte array representation of the string "Encryption".
065         */
066        private static final byte[] ENCRYPTION_BYTES = {
067
068                (byte)69, (byte)110, (byte)99, (byte)114, (byte)121, (byte)112, (byte)116, (byte)105, (byte)111, (byte)110
069        };
070
071
072        /**
073         * The byte array representation of the string "Integrity".
074         */
075        private static final byte[] INTEGRITY_BYTES = {
076
077                (byte)73, (byte)110, (byte)116, (byte)101, (byte)103, (byte)114, (byte)105, (byte)116, (byte)121
078        };
079
080
081        /**
082         * Generates a Content Encryption Key (CEK) from the specified
083         * Content Master Key (CMK) and JOSE encryption method.
084         *
085         * @param key The Content Master Key (CMK). Must not be {@code null}.
086         * @param enc The JOSE encryption method. Must not be {@code null}.
087         * @param epu The value of the encryption PartyUInfo header parameter,
088         *            {@code null} if not specified.
089         * @param epv The value of the encryption PartyVInfo header parameter,
090         *            {@code null} if not specified.
091         *
092         * @return The generated AES CEK.
093         *
094         * @throws JOSEException If CEK generation failed.
095         */
096        public static SecretKey generateCEK(final SecretKey key,
097                                            final EncryptionMethod enc,
098                                            final byte[] epu,
099                                            final byte[] epv)
100                throws JOSEException {
101
102                ByteArrayOutputStream baos = new ByteArrayOutputStream();
103
104                int hashBitLength;
105
106                try {
107                        // Write [0, 0, 0, 1]
108                        baos.write(ONE_BYTES);
109
110                        // Append CMK
111                        byte[] cmkBytes = key.getEncoded();
112                        baos.write(cmkBytes);
113
114                        // Append [CEK-bit-length...]
115                        final int cmkBitLength = cmkBytes.length * 8;
116                        hashBitLength = cmkBitLength;
117                        final int cekBitLength = cmkBitLength / 2;
118                        byte[] cekBitLengthBytes = IntegerUtils.toBytes(cekBitLength);
119                        baos.write(cekBitLengthBytes);
120
121                        // Append the encryption method value, e.g. "A128CBC+HS256"
122                        byte[] encBytes = enc.toString().getBytes(StandardCharset.UTF_8);
123                        baos.write(encBytes);
124
125                        // Append encryption PartyUInfo=Datalen || Data
126                        if (epu != null) {
127
128                                baos.write(IntegerUtils.toBytes(epu.length));
129                                baos.write(epu);
130
131                        } else {
132                                baos.write(ZERO_BYTES);
133                        }
134
135                        // Append encryption PartyVInfo=Datalen || Data
136                        if (epv != null) {
137
138                                baos.write(IntegerUtils.toBytes(epv.length));
139                                baos.write(epv);
140
141                        } else {
142                                baos.write(ZERO_BYTES);
143                        }
144
145                        // Append "Encryption" label
146                        baos.write(ENCRYPTION_BYTES);
147
148                } catch (IOException e) {
149
150                        throw new JOSEException(e.getMessage(), e);
151                }
152
153                // Write out
154                byte[] hashInput = baos.toByteArray();
155
156                MessageDigest md;
157
158                try {
159                        // SHA-256 or SHA-512
160                        md = MessageDigest.getInstance("SHA-" + hashBitLength);
161
162                } catch (NoSuchAlgorithmException e) {
163
164                        throw new JOSEException(e.getMessage(), e);
165                }
166
167                byte[] hashOutput = md.digest(hashInput);
168
169                byte[] cekBytes = new byte[hashOutput.length / 2];
170                System.arraycopy(hashOutput, 0, cekBytes, 0, cekBytes.length);
171
172                return new SecretKeySpec(cekBytes, "AES");
173        }
174
175
176        /**
177         * Generates a Content Integrity Key (CIK) from the specified
178         * Content Master Key (CMK) and JOSE encryption method.
179         *
180         * @param key The Content Master Key (CMK). Must not be {@code null}.
181         * @param enc The JOSE encryption method. Must not be {@code null}.
182         * @param epu The value of the encryption PartyUInfo header parameter,
183         *            {@code null} if not specified.
184         * @param epv The value of the encryption PartyVInfo header parameter,
185         *            {@code null} if not specified.
186         *
187         * @return The generated HMAC SHA CIK.
188         *
189         * @throws JOSEException If CIK generation failed.
190         */
191        public static SecretKey generateCIK(final SecretKey key,
192                                            final EncryptionMethod enc,
193                                            final byte[] epu,
194                                            final byte[] epv)
195                throws JOSEException {
196
197                ByteArrayOutputStream baos = new ByteArrayOutputStream();
198
199                int hashBitLength;
200                int cikBitLength;
201
202                try {
203                        // Write [0, 0, 0, 1]
204                        baos.write(ONE_BYTES);
205
206                        // Append CMK
207                        byte[] cmkBytes = key.getEncoded();
208                        baos.write(cmkBytes);
209
210                        // Append [CIK-bit-length...]
211                        final int cmkBitLength = cmkBytes.length * 8;
212                        hashBitLength = cmkBitLength;
213                        cikBitLength = cmkBitLength;
214                        byte[] cikBitLengthBytes = IntegerUtils.toBytes(cikBitLength);
215                        baos.write(cikBitLengthBytes);
216
217                        // Append the encryption method value, e.g. "A128CBC+HS256"
218                        byte[] encBytes = enc.toString().getBytes(StandardCharset.UTF_8);
219                        baos.write(encBytes);
220
221                        // Append encryption PartyUInfo=Datalen || Data
222                        if (epu != null) {
223
224                                baos.write(IntegerUtils.toBytes(epu.length));
225                                baos.write(epu);
226
227                        } else {
228                                baos.write(ZERO_BYTES);
229                        }
230
231                        // Append encryption PartyVInfo=Datalen || Data
232                        if (epv != null) {
233
234                                baos.write(IntegerUtils.toBytes(epv.length));
235                                baos.write(epv);
236
237                        } else {
238                                baos.write(ZERO_BYTES);
239                        }
240
241                        // Append "Encryption" label
242                        baos.write(INTEGRITY_BYTES);
243
244                } catch (IOException e) {
245
246                        throw new JOSEException(e.getMessage(), e);
247                }
248
249                // Write out
250                byte[] hashInput = baos.toByteArray();
251
252                MessageDigest md;
253
254                try {
255                        // SHA-256 or SHA-512
256                        md = MessageDigest.getInstance("SHA-" + hashBitLength);
257
258                } catch (NoSuchAlgorithmException e) {
259
260                        throw new JOSEException(e.getMessage(), e);
261                }
262
263                // HMACSHA256 or HMACSHA512
264                return new SecretKeySpec(md.digest(hashInput), "HMACSHA" + cikBitLength);
265        }
266
267
268        /**
269         * Prevents public instantiation.
270         */
271        private LegacyConcatKDF() {
272
273        }
274}