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.nio.charset.StandardCharsets; 022import java.security.InvalidKeyException; 023import java.security.NoSuchAlgorithmException; 024import java.security.PrivateKey; 025import java.security.Provider; 026import java.security.interfaces.ECPublicKey; 027import javax.crypto.KeyAgreement; 028import javax.crypto.SecretKey; 029import javax.crypto.spec.SecretKeySpec; 030 031import com.google.crypto.tink.subtle.X25519; 032 033import com.nimbusds.jose.EncryptionMethod; 034import com.nimbusds.jose.JOSEException; 035import com.nimbusds.jose.JWEAlgorithm; 036import com.nimbusds.jose.JWEHeader; 037import com.nimbusds.jose.jwk.Curve; 038import com.nimbusds.jose.jwk.OctetKeyPair; 039 040 041/** 042 * Elliptic Curve Diffie-Hellman key agreement functions and utilities. 043 * 044 * @author Vladimir Dzhuvinov 045 * @version 2018-12-12 046 */ 047public class ECDH { 048 049 050 /** 051 * Enumeration of the Elliptic Curve Diffie-Hellman Ephemeral Static 052 * algorithm modes. 053 */ 054 public enum AlgorithmMode { 055 056 /** 057 * Direct key agreement mode. 058 */ 059 DIRECT, 060 061 062 /** 063 * Key wrapping mode. 064 */ 065 KW 066 } 067 068 069 /** 070 * Resolves the ECDH algorithm mode. 071 * 072 * @param alg The JWE algorithm. Must be supported and not 073 * {@code null}. 074 * 075 * @return The algorithm mode. 076 * 077 * @throws JOSEException If the JWE algorithm is not supported. 078 */ 079 public static AlgorithmMode resolveAlgorithmMode(final JWEAlgorithm alg) 080 throws JOSEException { 081 082 if (alg.equals(JWEAlgorithm.ECDH_ES)) { 083 084 return AlgorithmMode.DIRECT; 085 086 } else if (alg.equals(JWEAlgorithm.ECDH_ES_A128KW) || 087 alg.equals(JWEAlgorithm.ECDH_ES_A192KW) || 088 alg.equals(JWEAlgorithm.ECDH_ES_A256KW)) { 089 090 return AlgorithmMode.KW; 091 } else { 092 093 throw new JOSEException(AlgorithmSupportMessage.unsupportedJWEAlgorithm( 094 alg, 095 ECDHCryptoProvider.SUPPORTED_ALGORITHMS)); 096 } 097 } 098 099 100 /** 101 * Returns the bit length of the shared key (derived via concat KDF) 102 * for the specified JWE ECDH algorithm. 103 * 104 * @param alg The JWE ECDH algorithm. Must be supported and not 105 * {@code null}. 106 * @param enc The encryption method. Must be supported} and not 107 * {@code null}. 108 * 109 * @return The bit length of the shared key. 110 * 111 * @throws JOSEException If the JWE algorithm or encryption method is 112 * not supported. 113 */ 114 public static int sharedKeyLength(final JWEAlgorithm alg, final EncryptionMethod enc) 115 throws JOSEException { 116 117 if (alg.equals(JWEAlgorithm.ECDH_ES)) { 118 119 int length = enc.cekBitLength(); 120 121 if (length == 0) { 122 throw new JOSEException("Unsupported JWE encryption method " + enc); 123 } 124 125 return length; 126 127 } else if (alg.equals(JWEAlgorithm.ECDH_ES_A128KW)) { 128 return 128; 129 } else if (alg.equals(JWEAlgorithm.ECDH_ES_A192KW)) { 130 return 192; 131 } else if (alg.equals(JWEAlgorithm.ECDH_ES_A256KW)) { 132 return 256; 133 } else { 134 throw new JOSEException(AlgorithmSupportMessage.unsupportedJWEAlgorithm( 135 alg, ECDHCryptoProvider.SUPPORTED_ALGORITHMS)); 136 } 137 } 138 139 140 /** 141 * Derives a shared secret (also called 'Z') from the specified ECDH 142 * key agreement. 143 * 144 * @param publicKey The public EC key, i.e. the consumer's public EC 145 * key on encryption, or the ephemeral public EC key 146 * on decryption. Must not be {@code null}. 147 * @param privateKey The private EC Key, i.e. the ephemeral private EC 148 * key on encryption, or the consumer's private EC 149 * key on decryption. Must not be {@code null}. 150 * @param provider The JCA provider for the ECDH key agreement, 151 * {@code null} to use the default. 152 * 153 * @return The derived shared secret ('Z'), with algorithm "AES". 154 * 155 * @throws JOSEException If derivation of the shared secret failed. 156 */ 157 public static SecretKey deriveSharedSecret(final ECPublicKey publicKey, 158 final PrivateKey privateKey, 159 final Provider provider) 160 throws JOSEException { 161 162 // Get an ECDH key agreement instance from the JCA provider 163 KeyAgreement keyAgreement; 164 165 try { 166 if (provider != null) { 167 keyAgreement = KeyAgreement.getInstance("ECDH", provider); 168 } else { 169 keyAgreement = KeyAgreement.getInstance("ECDH"); 170 } 171 172 } catch (NoSuchAlgorithmException e) { 173 throw new JOSEException("Couldn't get an ECDH key agreement instance: " + e.getMessage(), e); 174 } 175 176 try { 177 keyAgreement.init(privateKey); 178 keyAgreement.doPhase(publicKey, true); 179 180 } catch (InvalidKeyException e) { 181 throw new JOSEException("Invalid key for ECDH key agreement: " + e.getMessage(), e); 182 } 183 184 return new SecretKeySpec(keyAgreement.generateSecret(), "AES"); 185 } 186 187 188 /** 189 * Derives a shared secret (also called 'Z') from the specified ECDH 190 * key agreement. 191 * 192 * @param publicKey The public OKP key, i.e. the consumer's public EC 193 * key on encryption, or the ephemeral public EC key 194 * on decryption. Must not be {@code null}. 195 * @param privateKey The private OKP key, i.e. the ephemeral private EC 196 * key on encryption, or the consumer's private EC 197 * key on decryption. Must not be {@code null}. 198 * 199 * @return The derived shared secret ('Z'), with algorithm "AES". 200 * 201 * @throws JOSEException If derivation of the shared secret failed. 202 */ 203 public static SecretKey deriveSharedSecret(final OctetKeyPair publicKey, final OctetKeyPair privateKey) 204 throws JOSEException { 205 206 if (publicKey.isPrivate()) { 207 throw new JOSEException("Expected public key but received OKP with 'd' value"); 208 } 209 210 if (! Curve.X25519.equals(publicKey.getCurve())) { 211 throw new JOSEException("Expected public key OKP with crv=X25519"); 212 } 213 214 if (! privateKey.isPrivate()) { 215 throw new JOSEException("Expected private key but received OKP without 'd' value"); 216 } 217 218 if (! Curve.X25519.equals(privateKey.getCurve())) { 219 throw new JOSEException("Expected private key OKP with crv=X25519"); 220 } 221 222 final byte[] privateKeyBytes = privateKey.getDecodedD(); 223 final byte[] publicKeyBytes = publicKey.getDecodedX(); 224 225 final byte[] sharedSecretBytes; 226 try { 227 sharedSecretBytes = X25519.computeSharedSecret(privateKeyBytes, publicKeyBytes); 228 } catch (InvalidKeyException e) { 229 throw new JOSEException(e.getMessage(), e); 230 } 231 232 return new SecretKeySpec(sharedSecretBytes, "AES"); 233 } 234 235 236 /** 237 * Derives a shared key (via concat KDF). 238 * 239 * @param header The JWE header. Its algorithm and encryption method 240 * must be supported. Must not be {@code null}. 241 * @param Z The derived shared secret ('Z'). Must not be 242 * {@code null}. 243 * @param concatKDF The concat KDF. Must be initialised and not 244 * {@code null}. 245 * 246 * @return The derived shared key. 247 * 248 * @throws JOSEException If derivation of the shared key failed. 249 */ 250 public static SecretKey deriveSharedKey(final JWEHeader header, 251 final SecretKey Z, 252 final ConcatKDF concatKDF) 253 throws JOSEException { 254 255 final int sharedKeyLength = sharedKeyLength(header.getAlgorithm(), header.getEncryptionMethod()); 256 257 // Set the alg ID for the concat KDF 258 AlgorithmMode algMode = resolveAlgorithmMode(header.getAlgorithm()); 259 260 final String algID; 261 262 if (algMode == AlgorithmMode.DIRECT) { 263 // algID = enc 264 algID = header.getEncryptionMethod().getName(); 265 } else if (algMode == AlgorithmMode.KW) { 266 // algID = alg 267 algID = header.getAlgorithm().getName(); 268 } else { 269 throw new JOSEException("Unsupported JWE ECDH algorithm mode: " + algMode); 270 } 271 272 return concatKDF.deriveKey( 273 Z, 274 sharedKeyLength, 275 ConcatKDF.encodeDataWithLength(algID.getBytes(StandardCharsets.US_ASCII)), 276 ConcatKDF.encodeDataWithLength(header.getAgreementPartyUInfo()), 277 ConcatKDF.encodeDataWithLength(header.getAgreementPartyVInfo()), 278 ConcatKDF.encodeIntData(sharedKeyLength), 279 ConcatKDF.encodeNoData()); 280 } 281 282 283 /** 284 * Prevents public instantiation. 285 */ 286 private ECDH() { 287 288 } 289}