1 /** 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 * SPDX-License-Identifier: Apache-2.0. 4 */ 5 package software.amazon.awssdk.crt.io; 6 7 import java.util.ArrayList; 8 import java.util.IllegalFormatException; 9 import java.util.List; 10 import java.util.function.Consumer; 11 12 import software.amazon.awssdk.crt.CrtResource; 13 import software.amazon.awssdk.crt.CrtRuntimeException; 14 import software.amazon.awssdk.crt.utils.PemUtils; 15 import software.amazon.awssdk.crt.utils.StringUtils; 16 17 /** 18 * This class wraps the aws_tls_connection_options from aws-c-io to provide 19 * access to TLS configuration contexts in the AWS Common Runtime. 20 */ 21 public final class TlsContextOptions extends CrtResource { 22 23 public enum TlsVersions { 24 /** 25 * SSL v3. This should almost never be used. 26 */ 27 SSLv3(0), 28 TLSv1(1), 29 /** 30 * TLS 1.1 31 */ 32 TLSv1_1(2), 33 /** 34 * TLS 1.2 35 */ 36 TLSv1_2(3), 37 /** 38 * TLS 1.3 39 */ 40 TLSv1_3(4), 41 /** 42 * Use whatever the system default is. This is usually the best option, as it will be automatically updated 43 * as the underlying OS or platform changes. 44 */ 45 TLS_VER_SYS_DEFAULTS(128); 46 47 private int version; TlsVersions(int val)48 TlsVersions(int val) { 49 version = val; 50 } 51 getValue()52 int getValue() { return version; } 53 } 54 55 /** 56 * Sets the minimum acceptable TLS version that the {@link TlsContext} will 57 * allow. Not compatible with setCipherPreference() API. 58 * 59 * Select from TlsVersions, a good default is TlsVersions.TLS_VER_SYS_DEFAULTS 60 * as this will update if the OS TLS is updated 61 */ 62 public TlsVersions minTlsVersion = TlsVersions.TLS_VER_SYS_DEFAULTS; 63 /** 64 * Sets the TLS Cipher Preferences that can be negotiated and used during the 65 * TLS Connection. Not compatible with setMinimumTlsVersion() API. 66 * 67 */ 68 public TlsCipherPreference tlsCipherPreference = TlsCipherPreference.TLS_CIPHER_SYSTEM_DEFAULT; 69 /** 70 * Sets the ALPN protocol list that will be provided when a TLS connection 71 * starts e.g. "x-amzn-mqtt-ca" 72 */ 73 public List<String> alpnList = new ArrayList<>(); 74 /** 75 * Set whether or not the peer should be verified. Default is true for clients, 76 * and false for servers. If you are in a development or debugging environment, 77 * you can disable this to avoid or diagnose trust store issues. This should 78 * always be true on clients in the wild. If you set this to true on a server, 79 * it will validate every client connection. 80 */ 81 public boolean verifyPeer = false; 82 83 private String certificate; 84 private String privateKey; 85 private String certificatePath; 86 private String privateKeyPath; 87 private String caRoot; 88 private String caFile; 89 private String caDir; 90 private String pkcs12Path; 91 private String pkcs12Password; 92 private TlsContextPkcs11Options pkcs11Options; 93 private TlsContextCustomKeyOperationOptions customKeyOperations; 94 private String windowsCertStorePath; 95 96 /** 97 * Creates a new set of options that can be used to create a {@link TlsContext} 98 */ TlsContextOptions()99 private TlsContextOptions() { 100 101 } 102 103 @Override getNativeHandle()104 public long getNativeHandle() { 105 if (super.getNativeHandle() == 0) { 106 if (tlsCipherPreference != TlsCipherPreference.TLS_CIPHER_SYSTEM_DEFAULT 107 && minTlsVersion != TlsVersions.TLS_VER_SYS_DEFAULTS) { 108 throw new IllegalStateException("tlsCipherPreference and minTlsVersion are mutually exclusive"); 109 } 110 acquireNativeHandle(tlsContextOptionsNew( 111 minTlsVersion.getValue(), 112 tlsCipherPreference.getValue(), 113 alpnList.size() > 0 ? StringUtils.join(";", alpnList) : null, 114 certificate, 115 privateKey, 116 certificatePath, 117 privateKeyPath, 118 caRoot, 119 caFile, 120 caDir, 121 verifyPeer, 122 pkcs12Path, 123 pkcs12Password, 124 pkcs11Options, 125 customKeyOperations, 126 windowsCertStorePath 127 )); 128 } 129 return super.getNativeHandle(); 130 } 131 132 /** 133 * Determines whether a resource releases its dependencies at the same time the native handle is released or if it waits. 134 * Resources that wait are responsible for calling releaseReferences() manually. 135 */ 136 @Override canReleaseReferencesImmediately()137 protected boolean canReleaseReferencesImmediately() { return true; } 138 139 /** 140 * Frees the native resources associated with this instance 141 */ 142 @Override releaseNativeHandle()143 protected void releaseNativeHandle() { 144 // It is perfectly acceptable for this to have never created a native resource 145 if (!isNull()) { 146 tlsContextOptionsDestroy(getNativeHandle()); 147 } 148 } 149 150 /** 151 * Sets the TLS cipher preferences to use in contexts using this configuration 152 * @param cipherPref cipher preferences to use 153 */ setCipherPreference(TlsCipherPreference cipherPref)154 public void setCipherPreference(TlsCipherPreference cipherPref) { 155 if(!isCipherPreferenceSupported(cipherPref)) { 156 throw new IllegalArgumentException("TlsCipherPreference is not supported on this platform: " + cipherPref.name()); 157 } 158 159 if (this.minTlsVersion != TlsVersions.TLS_VER_SYS_DEFAULTS && cipherPref != TlsCipherPreference.TLS_CIPHER_SYSTEM_DEFAULT) { 160 throw new IllegalArgumentException("Currently only setMinimumTlsVersion() or setCipherPreference() may be used, not both."); 161 } 162 163 this.tlsCipherPreference = cipherPref; 164 } 165 166 /** 167 * Sets the path to the certificate that identifies this mutual TLS (mTLS) host. Must be in PEM format. 168 * @param certificatePath Path to PEM format certificate 169 * @param privateKeyPath Path to PEM format private key 170 */ initMtlsFromPath(String certificatePath, String privateKeyPath)171 public void initMtlsFromPath(String certificatePath, String privateKeyPath) { 172 this.certificatePath = certificatePath; 173 this.privateKeyPath = privateKeyPath; 174 } 175 176 /** 177 * Sets the certificate/key pair that identifies this mutual TLS (mTLS) host. Must be in 178 * PEM format. 179 * 180 * @param certificate PEM armored certificate 181 * @param privateKey PEM armored private key 182 * @throws IllegalArgumentException If the certificate or privateKey are not in PEM format or if they contain chains 183 */ initMtls(String certificate, String privateKey)184 public void initMtls(String certificate, String privateKey) throws IllegalArgumentException { 185 this.certificate = PemUtils.cleanUpPem(certificate); 186 PemUtils.sanityCheck(certificate, 1, "CERTIFICATE"); 187 188 this.privateKey = PemUtils.cleanUpPem(privateKey); 189 PemUtils.sanityCheck(privateKey, 1, "PRIVATE KEY"); 190 } 191 192 /** 193 * Apple platforms only - Initializes mutual TLS (mTLS) with PKCS12 file and password 194 * @param pkcs12Path Path to PKCS12 file 195 * @param pkcs12Password PKCS12 password 196 */ initMtlsPkcs12(String pkcs12Path, String pkcs12Password)197 public void initMtlsPkcs12(String pkcs12Path, String pkcs12Password) { 198 if (this.certificate != null || this.privateKey != null || this.certificatePath != null 199 || this.privateKeyPath != null) { 200 throw new IllegalArgumentException( 201 "PKCS#12 and mTLS via certificate/private key pair are mutually exclusive"); 202 } 203 this.pkcs12Path = pkcs12Path; 204 this.pkcs12Password = pkcs12Password; 205 } 206 207 /** 208 * Returns whether or not ALPN is supported on the current platform 209 * @return true if ALPN is supported, false otherwise 210 */ isAlpnSupported()211 public static boolean isAlpnSupported() { 212 return tlsContextOptionsIsAlpnAvailable(); 213 } 214 215 /** 216 * Returns whether or not the current platform can be configured to a specific TlsCipherPreference. 217 * @param cipherPref The TlsCipherPreference to check 218 * @return True if the current platform does support this TlsCipherPreference, false otherwise 219 */ isCipherPreferenceSupported(TlsCipherPreference cipherPref)220 public static boolean isCipherPreferenceSupported(TlsCipherPreference cipherPref) { 221 return tlsContextOptionsIsCipherPreferenceSupported(cipherPref.getValue()); 222 } 223 224 /** 225 * Helper function to provide a TlsContext-local trust store 226 * @param caPath Path to the local trust store. Can be null. 227 * @param caFile Path to the root certificate. Must be in PEM format. 228 */ overrideDefaultTrustStoreFromPath(String caPath, String caFile)229 public void overrideDefaultTrustStoreFromPath(String caPath, String caFile) { 230 if (this.caRoot != null) { 231 throw new IllegalArgumentException("Certificate authority is already specified via PEM buffer"); 232 } 233 this.caDir = caPath; 234 this.caFile = caFile; 235 } 236 237 /** 238 * Helper function to provide a TlsContext-local trust store 239 * 240 * @param caRoot Buffer containing the root certificate chain. Must be in PEM format. 241 * @throws IllegalArgumentException if the CA Root PEM file is malformed 242 */ overrideDefaultTrustStore(String caRoot)243 public void overrideDefaultTrustStore(String caRoot) throws IllegalArgumentException { 244 if (this.caFile != null || this.caDir != null) { 245 throw new IllegalArgumentException("Certificate authority is already specified via path(s)"); 246 } 247 this.caRoot = PemUtils.cleanUpPem(caRoot); 248 // 1024 certs in the chain is the default supported by s2n: 249 PemUtils.sanityCheck(this.caRoot, 1024, "CERTIFICATE"); 250 } 251 252 /** 253 * Helper which creates a default set of TLS options for the current platform 254 * @return A default configured set of options for a TLS client connection 255 */ createDefaultClient()256 public static TlsContextOptions createDefaultClient() { 257 TlsContextOptions options = new TlsContextOptions(); 258 options.verifyPeer = true; 259 return options; 260 } 261 262 /** 263 * Helper which creates a default set of TLS options for the current platform 264 * 265 * @return A default configured set of options for a TLS server connection 266 */ createDefaultServer()267 public static TlsContextOptions createDefaultServer() { 268 TlsContextOptions options = new TlsContextOptions(); 269 options.verifyPeer = false; 270 return options; 271 } 272 273 /** 274 * Helper which creates mutual TLS (mTLS) options using a certificate and private key 275 * @param certificatePath Path to a PEM format certificate 276 * @param privateKeyPath Path to a PEM format private key 277 * @return A set of options for setting up an mTLS connection 278 */ createWithMtlsFromPath(String certificatePath, String privateKeyPath)279 public static TlsContextOptions createWithMtlsFromPath(String certificatePath, String privateKeyPath) { 280 TlsContextOptions options = new TlsContextOptions(); 281 options.initMtlsFromPath(certificatePath, privateKeyPath); 282 options.verifyPeer = true; 283 return options; 284 } 285 286 /** 287 * Helper which creates mutual TLS (mTLS) options using a certificate and private key 288 * 289 * @param certificate String containing a PEM format certificate 290 * @param privateKey String containing a PEM format private key 291 * @return A set of options for setting up an mTLS connection 292 * @throws IllegalArgumentException If either PEM fails to parse 293 */ createWithMtls(String certificate, String privateKey)294 public static TlsContextOptions createWithMtls(String certificate, String privateKey) 295 throws IllegalArgumentException { 296 TlsContextOptions options = new TlsContextOptions(); 297 options.initMtls(certificate, privateKey); 298 options.verifyPeer = true; 299 return options; 300 } 301 302 /** 303 * Apple platforms only - Helper which creates mutual TLS (mTLS) options using PKCS12 304 * @param pkcs12Path The path to a PKCS12 file @see #setPkcs12Path(String) 305 * @param pkcs12Password The PKCS12 password @see #setPkcs12Password(String) 306 * @return A set of options for creating a PKCS12 mTLS connection 307 */ createWithMtlsPkcs12(String pkcs12Path, String pkcs12Password)308 public static TlsContextOptions createWithMtlsPkcs12(String pkcs12Path, String pkcs12Password) { 309 TlsContextOptions options = new TlsContextOptions(); 310 options.initMtlsPkcs12(pkcs12Path, pkcs12Password); 311 options.verifyPeer = true; 312 return options; 313 } 314 315 /** 316 * Unix platforms only - Helper which creates mutual TLS (mTLS) options using a PKCS#11 library for private key operations. 317 * @param pkcs11Options PKCS#11 options 318 * @return A set of options for creating a PKCS#11 mTLS connection 319 */ createWithMtlsPkcs11(TlsContextPkcs11Options pkcs11Options)320 public static TlsContextOptions createWithMtlsPkcs11(TlsContextPkcs11Options pkcs11Options) { 321 TlsContextOptions options = new TlsContextOptions(); 322 options.withMtlsPkcs11(pkcs11Options); 323 options.verifyPeer = true; 324 return options; 325 } 326 327 /** 328 * Unix platforms only - Helper which creates mutual TLS (mTLS) options using the applied custom key operations. This 329 * allows you to perform custom private key operations such as signing and decrypting. This is necessary if you 330 * require an external library to handle private key operations. 331 * 332 * @param custom The options for the custom private key operations 333 * @return A set of options for creating a custom key operation mTLS connection 334 */ createWithMtlsCustomKeyOperations(TlsContextCustomKeyOperationOptions custom)335 public static TlsContextOptions createWithMtlsCustomKeyOperations(TlsContextCustomKeyOperationOptions custom) { 336 TlsContextOptions options = new TlsContextOptions(); 337 options.withMtlsCustomKeyOperations(custom); 338 options.verifyPeer = true; 339 return options; 340 } 341 342 /** 343 * Windows platforms only - Helper which creates mutual TLS (mTLS) options using a 344 * certificate in a Windows certificate store. 345 * 346 * @param certificatePath Path to certificate in a Windows certificate store. 347 * The path must use backslashes and end with the 348 * certificate's thumbprint. Example: 349 * {@code CurrentUser\MY\A11F8A9B5DF5B98BA3508FBCA575D09570E0D2C6} 350 * @return A set of options for setting up an mTLS connection 351 */ createWithMtlsWindowsCertStorePath(String certificatePath)352 public static TlsContextOptions createWithMtlsWindowsCertStorePath(String certificatePath) { 353 TlsContextOptions options = new TlsContextOptions(); 354 options.withMtlsWindowsCertStorePath(certificatePath); 355 options.verifyPeer = true; 356 return options; 357 } 358 359 /** 360 * Helper which creates mutual TLS (mTLS) options using a certificate and private key 361 * stored in a Java keystore. 362 * Will throw an exception if there is no certificate and key at the given certificate alias, or there is some other 363 * error accessing or using the passed-in Java keystore. 364 * 365 * Note: function assumes the passed keystore has already been loaded from a file by calling "keystore.load()" or similar. 366 * 367 * @param keyStore The Java keystore to use. Assumed to be loaded with the desired certificate and key 368 * @param certificateAlias The alias of the certificate and key to use. 369 * @param certificatePassword The password of the certificate and key to use. 370 * @throws CrtRuntimeException if the certificate alias does not exist or the certificate/key cannot be found in the certificate alias 371 * @return A set of options for setting up an mTLS connection 372 */ createWithMtlsJavaKeystore( java.security.KeyStore keyStore, String certificateAlias, String certificatePassword)373 public static TlsContextOptions createWithMtlsJavaKeystore( 374 java.security.KeyStore keyStore, String certificateAlias, String certificatePassword) { 375 376 TlsContextOptions options = new TlsContextOptions(); 377 String certificate; 378 try { 379 java.security.cert.Certificate certificateData = keyStore.getCertificate(certificateAlias); 380 if (certificateData == null) { 381 throw new CrtRuntimeException("Certificate at given certificate alias does not exist or does not contain a certificate"); 382 } 383 String certificateString = new String(StringUtils.base64Encode(certificateData.getEncoded())); 384 certificate = "-----BEGIN CERTIFICATE-----\n" + certificateString + "-----END CERTIFICATE-----\n"; 385 } catch (java.security.KeyStoreException | java.security.cert.CertificateEncodingException ex) { 386 throw new RuntimeException("Failed to get certificate from Java keystore", ex); 387 } 388 String privateKey; 389 try { 390 java.security.Key keyData = keyStore.getKey(certificateAlias, certificatePassword.toCharArray()); 391 if (keyData == null) { 392 throw new CrtRuntimeException("Private key at given certificate alias does not exist or does not identify a key-related entity"); 393 } 394 String keyString = new String(StringUtils.base64Encode(keyData.getEncoded())); 395 privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" + keyString + "-----END RSA PRIVATE KEY-----\n"; 396 } catch (java.security.KeyStoreException | java.security.NoSuchAlgorithmException | java.security.UnrecoverableKeyException ex) { 397 throw new RuntimeException("Failed to get private key from Java keystore", ex); 398 } 399 options.initMtls(certificate, privateKey); 400 options.verifyPeer = true; 401 return options; 402 } 403 404 /******************************************************************************* 405 * .with() methods 406 ******************************************************************************/ 407 408 /** 409 * Sets the ciphers that the TlsContext will be able to use 410 * @param cipherPref The preference set of ciphers to use 411 * @return this 412 */ withCipherPreference(TlsCipherPreference cipherPref)413 public TlsContextOptions withCipherPreference(TlsCipherPreference cipherPref) { 414 setCipherPreference(cipherPref); 415 return this; 416 } 417 418 /** 419 * Sets the minimum TLS version that the TlsContext will allow. Defaults to 420 * OS defaults. 421 * @param version Minimum acceptable TLS version 422 * @return this 423 */ withMinimumTlsVersion(TlsVersions version)424 public TlsContextOptions withMinimumTlsVersion(TlsVersions version) { 425 minTlsVersion = version; 426 return this; 427 } 428 429 /** 430 * Sets the ALPN protocols list for any connections using this TlsContext 431 * @param alpnList Semi-colon delimited list of supported ALPN protocols 432 * @return this 433 */ withAlpnList(String alpnList)434 public TlsContextOptions withAlpnList(String alpnList) { 435 String[] parts = alpnList.split(";"); 436 for (String part : parts) { 437 this.alpnList.add(part); 438 } 439 return this; 440 } 441 442 /** 443 * Enables mutual TLS (mTLS) on this TlsContext 444 * @param certificate mTLS certificate, in PEM format 445 * @param privateKey mTLS private key, in PEM format 446 * @return this 447 */ withMtls(String certificate, String privateKey)448 public TlsContextOptions withMtls(String certificate, String privateKey) { 449 this.initMtls(certificate, privateKey); 450 return this; 451 } 452 453 /** 454 * Enables mutual TLS (mTLS) on this TlsContext 455 * @param certificatePath path to mTLS certificate, in PEM format 456 * @param privateKeyPath path to mTLS private key, in PEM format 457 * @return this 458 */ withMtlsFromPath(String certificatePath, String privateKeyPath)459 public TlsContextOptions withMtlsFromPath(String certificatePath, String privateKeyPath) { 460 this.initMtlsFromPath(certificatePath, privateKeyPath); 461 return this; 462 } 463 464 /** 465 * Specifies the certificate authority to use. By default, the OS CA repository will be used. 466 * @param caRoot Certificate Authority, in PEM format 467 * @return this 468 */ withCertificateAuthority(String caRoot)469 public TlsContextOptions withCertificateAuthority(String caRoot) { 470 this.overrideDefaultTrustStore(caRoot); 471 return this; 472 } 473 474 /** 475 * Specifies the certificate authority to use. 476 * @param caDirPath Path to certificate directory, e.g. /etc/ssl/certs 477 * @param caFilePath Path to ceritificate authority, in PEM format 478 * @return this 479 */ withCertificateAuthorityFromPath(String caDirPath, String caFilePath)480 public TlsContextOptions withCertificateAuthorityFromPath(String caDirPath, String caFilePath) { 481 this.overrideDefaultTrustStoreFromPath(caDirPath, caFilePath); 482 return this; 483 } 484 485 /** 486 * Apple platforms only, specifies mutual TLS (mTLS) using PKCS#12 487 * @param pkcs12Path Path to PKCS#12 certificate, in PEM format 488 * @param pkcs12Password PKCS#12 password 489 * @return this 490 */ withMtlsPkcs12(String pkcs12Path, String pkcs12Password)491 public TlsContextOptions withMtlsPkcs12(String pkcs12Path, String pkcs12Password) { 492 this.initMtlsPkcs12(pkcs12Path, pkcs12Password); 493 return this; 494 } 495 496 /** 497 * Unix platforms only, specifies mutual TLS (mTLS) using a PKCS#11 library for private key operations. 498 * @param pkcs11Options PKCS#11 options 499 * @return this 500 */ withMtlsPkcs11(TlsContextPkcs11Options pkcs11Options)501 public TlsContextOptions withMtlsPkcs11(TlsContextPkcs11Options pkcs11Options) { 502 swapReferenceTo(this.pkcs11Options, pkcs11Options); 503 this.pkcs11Options = pkcs11Options; 504 return this; 505 } 506 507 /** 508 * Unix platforms only, specifies TLS options for custom private key operations. This 509 * allows you to perform custom private key operations such as signing and decrypting. 510 * 511 * @param customKeyOperations The custom private key operations 512 * @return this 513 */ withMtlsCustomKeyOperations(TlsContextCustomKeyOperationOptions customKeyOperations)514 public TlsContextOptions withMtlsCustomKeyOperations(TlsContextCustomKeyOperationOptions customKeyOperations) { 515 this.customKeyOperations = customKeyOperations; 516 return this; 517 } 518 519 /** 520 * Windows platforms only, specifies mutual TLS (mTLS) using a certificate in a Windows 521 * certificate store. 522 * 523 * @param certificatePath Path to certificate in a Windows certificate store. 524 * The path must use backslashes and end with the 525 * certificate's thumbprint. Example: 526 * {@code CurrentUser\MY\A11F8A9B5DF5B98BA3508FBCA575D09570E0D2C6} 527 * @return this 528 */ withMtlsWindowsCertStorePath(String certificatePath)529 public TlsContextOptions withMtlsWindowsCertStorePath(String certificatePath) { 530 this.windowsCertStorePath = certificatePath; 531 return this; 532 } 533 534 /** 535 * Sets whether or not TLS will validate the certificate from the peer. On clients, 536 * this is enabled by default. On servers, this is disabled by default. 537 * @param verify true to verify peers, false to ignore certs 538 * @return this 539 */ withVerifyPeer(boolean verify)540 public TlsContextOptions withVerifyPeer(boolean verify) { 541 this.verifyPeer = verify; 542 return this; 543 } 544 545 /** 546 * Enables TLS peer verification of certificates 547 * @see TlsContextOptions#withVerifyPeer(boolean) 548 * @return this 549 */ withVerifyPeer()550 public TlsContextOptions withVerifyPeer() { 551 return this.withVerifyPeer(true); 552 } 553 554 /******************************************************************************* 555 * native methods 556 ******************************************************************************/ tlsContextOptionsNew( int minTlsVersion, int cipherPref, String alpn, String certificate, String privateKey, String certificatePath, String privateKeyPath, String caRoot, String caFile, String caDir, boolean verifyPeer, String pkcs12Path, String pkcs12Password, TlsContextPkcs11Options pkcs11Options, TlsContextCustomKeyOperationOptions customKeyOperation, String windowsCertStorePath )557 private static native long tlsContextOptionsNew( 558 int minTlsVersion, 559 int cipherPref, 560 String alpn, 561 String certificate, 562 String privateKey, 563 String certificatePath, 564 String privateKeyPath, 565 String caRoot, 566 String caFile, 567 String caDir, 568 boolean verifyPeer, 569 String pkcs12Path, 570 String pkcs12Password, 571 TlsContextPkcs11Options pkcs11Options, 572 TlsContextCustomKeyOperationOptions customKeyOperation, 573 String windowsCertStorePath 574 ); 575 tlsContextOptionsDestroy(long elg)576 private static native void tlsContextOptionsDestroy(long elg); 577 tlsContextOptionsIsAlpnAvailable()578 private static native boolean tlsContextOptionsIsAlpnAvailable(); 579 tlsContextOptionsIsCipherPreferenceSupported(int cipherPref)580 private static native boolean tlsContextOptionsIsCipherPreferenceSupported(int cipherPref); 581 582 }; 583