diff --git a/.gitmodules b/.gitmodules index 72b85a4a..e024cd18 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,7 @@ [submodule "packages/react-native-quick-crypto/deps/blake3"] path = packages/react-native-quick-crypto/deps/blake3 url = https://github.com/BLAKE3-team/BLAKE3.git +[submodule "packages/react-native-quick-crypto/deps/ncrypto"] + path = packages/react-native-quick-crypto/deps/ncrypto + url = https://github.com/boorad/ncrypto.git + branch = fix/use-BN_GENCB_get_arg diff --git a/docs/implementation-coverage.md b/docs/implementation-coverage.md index 14f68865..73ace9e2 100644 --- a/docs/implementation-coverage.md +++ b/docs/implementation-coverage.md @@ -1,11 +1,17 @@ # Implementation Coverage - NodeJS This document attempts to describe the implementation status of Crypto APIs/Interfaces from Node.js in the `react-native-quick-crypto` library. -> Note: This is the status for version 1.x and higher. For version `0.x` see [this document](https://github.com/margelo/react-native-quick-crypto/blob/0.x/docs/implementation-coverage.md) and the [0.x branch](https://github.com/margelo/react-native-quick-crypto/tree/0.x). - * ` ` - not implemented in Node * ❌ - implemented in Node, not RNQC * ✅ - implemented in Node and RNQC +* 🚧 - work in progress + +## Post-Quantum Cryptography (PQC) + +- **ML-DSA** (Module Lattice Digital Signature Algorithm, FIPS 204) - ML-DSA-44, ML-DSA-65, ML-DSA-87 +- **ML-KEM** (Module Lattice Key Encapsulation Mechanism, FIPS 203) - ML-KEM-512, ML-KEM-768, ML-KEM-1024 + +These algorithms provide quantum-resistant cryptography. # `Crypto` @@ -147,13 +153,11 @@ This document attempts to describe the implementation status of Crypto APIs/Inte * ❌ `crypto.secureHeapUsed()` * ❌ `crypto.setEngine(engine[, flags])` * ❌ `crypto.setFips(bool)` - * 🚧 `crypto.sign(algorithm, data, key[, callback])` + * ✅ `crypto.sign(algorithm, data, key[, callback])` * 🚧 `crypto.subtle` (see below) * ❌ `crypto.timingSafeEqual(a, b)` - * 🚧 `crypto.verify(algorithm, data, key, signature[, callback])` - * ❌ `crypto.webcrypto` (see below) - -🚧 Details below still a work in progress 🚧 + * ✅ `crypto.verify(algorithm, data, key, signature[, callback])` + * 🚧 `crypto.webcrypto` (see below) ## `crypto.diffieHellman` | type | Status | @@ -204,22 +208,22 @@ This document attempts to describe the implementation status of Crypto APIs/Inte ## `crypto.sign` | Algorithm | Status | | --------- | :----: | -| `RSASSA-PKCS1-v1_5` | ❌ | -| `RSA-PSS` | ❌ | -| `ECDSA` | ❌ | +| `RSASSA-PKCS1-v1_5` | ✅ | +| `RSA-PSS` | ✅ | +| `ECDSA` | ✅ | | `Ed25519` | ✅ | | `Ed448` | ✅ | -| `HMAC` | ❌ | +| `HMAC` | ✅ | ## `crypto.verify` | Algorithm | Status | | --------- | :----: | -| `RSASSA-PKCS1-v1_5` | ❌ | -| `RSA-PSS` | ❌ | -| `ECDSA` | ❌ | +| `RSASSA-PKCS1-v1_5` | ✅ | +| `RSA-PSS` | ✅ | +| `ECDSA` | ✅ | | `Ed25519` | ✅ | | `Ed448` | ✅ | -| `HMAC` | ❌ | +| `HMAC` | ✅ | # `WebCrypto` @@ -240,7 +244,7 @@ This document attempts to describe the implementation status of Crypto APIs/Inte # `SubtleCrypto` -* 🚧 Class: `SubtleCrypto` +* ❌ Class: `SubtleCrypto` * ❌ static `supports(operation, algorithm[, lengthOrAdditionalAlgorithm])` * ❌ `subtle.decapsulateBits(decapsulationAlgorithm, decapsulationKey, ciphertext)` * ❌ `subtle.decapsulateKey(decapsulationAlgorithm, decapsulationKey, ciphertext, sharedKeyAlgorithm, extractable, usages)` @@ -255,9 +259,9 @@ This document attempts to describe the implementation status of Crypto APIs/Inte * 🚧 `subtle.generateKey(algorithm, extractable, keyUsages)` * ❌ `subtle.getPublicKey(key, keyUsages)` * 🚧 `subtle.importKey(format, keyData, algorithm, extractable, keyUsages)` - * 🚧 `subtle.sign(algorithm, key, data)` + * ✅ `subtle.sign(algorithm, key, data)` * ❌ `subtle.unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgo, unwrappedKeyAlgo, extractable, keyUsages)` - * 🚧 `subtle.verify(algorithm, key, signature, data)` + * ✅ `subtle.verify(algorithm, key, signature, data)` * ❌ `subtle.wrapKey(format, key, wrappingKey, wrapAlgo)` ## `subtle.decrypt` @@ -311,27 +315,27 @@ This document attempts to describe the implementation status of Crypto APIs/Inte ## `subtle.exportKey` | Key Type | `spki` | `pkcs8` | `jwk` | `raw` | `raw-secret` | `raw-public` | `raw-seed` | -| ------------------- | :----: | :-----: | :---: | :---: | :---: | :---: | :---: | -| `AES-CBC` | | | ✅ | ✅ | ✅ | | | -| `AES-CTR` | | | ✅ | ✅ | ✅ | | | -| `AES-GCM` | | | ✅ | ✅ | ✅ | | | -| `AES-KW` | | | ✅ | ✅ | ✅ | | | -| `AES-OCB` | | | ❌ | | ❌ | | | -| `ChaCha20-Poly1305` | | | ❌ | | ❌ | | | -| `ECDH` | ✅ | ✅ | ✅ | ✅ | | ✅ | | -| `ECDSA` | ✅ | ✅ | ✅ | ✅ | | ✅ | | -| `Ed25519` | ❌ | ❌ | ❌ | ❌ | | ❌ | | -| `Ed448` | ❌ | ❌ | ❌ | ❌ | | ❌ | | -| `HMAC` | | | ✅ | ✅ | ✅ | | | -| `ML-DSA-44` | ❌ | ❌ | ❌ | | | ❌ | ❌ | -| `ML-DSA-65` | ❌ | ❌ | ❌ | | | ❌ | ❌ | -| `ML-DSA-87` | ❌ | ❌ | ❌ | | | ❌ | ❌ | -| `ML-KEM-512` | ❌ | ❌ | | | | ❌ | ❌ | -| `ML-KEM-768` | ❌ | ❌ | | | | ❌ | ❌ | -| `ML-KEM-1024` | ❌ | ❌ | | | | ❌ | ❌ | -| `RSA-OAEP` | ✅ | ✅ | ✅ | | | | | -| `RSA-PSS` | ✅ | ✅ | ✅ | | | | | -| `RSASSA-PKCS1-v1_5` | ✅ | ✅ | ✅ | | | | | +| ------------------- | :----: | :-----: | :---: | :---: | :----------: | :----------: | :--------: | +| `AES-CBC` | | | ✅ | ✅ | ✅ | | | +| `AES-CTR` | | | ✅ | ✅ | ✅ | | | +| `AES-GCM` | | | ✅ | ✅ | ✅ | | | +| `AES-KW` | | | ✅ | ✅ | ✅ | | | +| `AES-OCB` | | | ❌ | | ❌ | | | +| `ChaCha20-Poly1305` | | | ❌ | | ❌ | | | +| `ECDH` | ✅ | ✅ | ✅ | ✅ | | ✅ | | +| `ECDSA` | ✅ | ✅ | ✅ | ✅ | | ✅ | | +| `Ed25519` | ✅ | ✅ | ❌ | ❌ | | ❌ | | +| `Ed448` | ✅ | ✅ | ❌ | ❌ | | ❌ | | +| `HMAC` | | | ✅ | ✅ | ✅ | | | +| `ML-DSA-44` | ✅ | ✅ | ✅ | | | ✅ | ✅ | +| `ML-DSA-65` | ✅ | ✅ | ✅ | | | ✅ | ✅ | +| `ML-DSA-87` | ✅ | ✅ | ✅ | | | ✅ | ✅ | +| `ML-KEM-512` | ❌ | ❌ | | | | ❌ | ❌ | +| `ML-KEM-768` | ❌ | ❌ | | | | ❌ | ❌ | +| `ML-KEM-1024` | ❌ | ❌ | | | | ❌ | ❌ | +| `RSA-OAEP` | ✅ | ✅ | ✅ | | | | | +| `RSA-PSS` | ✅ | ✅ | ✅ | | | | | +| `RSASSA-PKCS1-v1_5` | ✅ | ✅ | ✅ | | | | | * ` ` - not implemented in Node * ❌ - implemented in Node, not RNQC @@ -346,9 +350,9 @@ This document attempts to describe the implementation status of Crypto APIs/Inte | `ECDSA` | ✅ | | `Ed25519` | ✅ | | `Ed448` | ✅ | -| `ML-DSA-44` | ❌ | -| `ML-DSA-65` | ❌ | -| `ML-DSA-87` | ❌ | +| `ML-DSA-44` | ✅ | +| `ML-DSA-65` | ✅ | +| `ML-DSA-87` | ✅ | | `ML-KEM-512` | ❌ | | `ML-KEM-768` | ❌ | | `ML-KEM-1024` | ❌ | @@ -367,48 +371,48 @@ This document attempts to describe the implementation status of Crypto APIs/Inte | `AES-KW` | ❌ | | `AES-OCB` | ❌ | | `ChaCha20-Poly1305` | ❌ | -| `HMAC` | ❌ | +| `HMAC` | ✅ | ## `subtle.importKey` | Key Type | `spki` | `pkcs8` | `jwk` | `raw` | `raw-secret` | `raw-public` | `raw-seed` | -| ------------------- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | -| `AES-CBC` | | | ✅ | ✅ | ✅ | | | -| `AES-CTR` | | | ✅ | ✅ | ✅ | | | -| `AES-GCM` | | | ✅ | ✅ | ✅ | | | -| `AES-KW` | | | ✅ | ✅ | ✅ | | | -| `AES-OCB` | | | ❌ | | ❌ | | | -| `ChaCha20-Poly1305` | | | ❌ | | ❌ | | | -| `ECDH` | ✅ | ✅ | ✅ | ✅ | | ✅ | | -| `ECDSA` | ✅ | ✅ | ✅ | ✅ | | ✅ | | -| `Ed25519` | ❌ | ❌ | ❌ | ❌ | | ❌ | | -| `Ed448` | ❌ | ❌ | ❌ | ❌ | | ❌ | | -| `HDKF` | | | | ❌ | ❌ | | | -| `HMAC` | | | ✅ | ✅ | ✅ | | | -| `ML-DSA-44` | ❌ | ❌ | ❌ | | | ❌ | ❌ | -| `ML-DSA-65` | ❌ | ❌ | ❌ | | | ❌ | ❌ | -| `ML-DSA-87` | ❌ | ❌ | ❌ | | | ❌ | ❌ | -| `ML-KEM-512` | ❌ | ❌ | | | | ❌ | ❌ | -| `ML-KEM-768` | ❌ | ❌ | | | | ❌ | ❌ | -| `ML-KEM-1024` | ❌ | ❌ | | | | ❌ | ❌ | -| `PBKDF2` | | | | ✅ | ✅ | | | -| `RSA-OAEP` | ✅ | ❌ | ✅ | | | | | -| `RSA-PSS` | ✅ | ❌ | ✅ | | | | | -| `RSASSA-PKCS1-v1_5` | ✅ | ❌ | ✅ | | | | | -| `X25519` | ❌ | ❌ | ❌ | ❌ | | ❌ | | -| `X448` | ❌ | ❌ | ❌ | ❌ | | ❌ | | +| ------------------- | :----: | :-----: | :---: | :---: | :----------: | :----------: | :--------: | +| `AES-CBC` | | | ✅ | ✅ | ✅ | | | +| `AES-CTR` | | | ✅ | ✅ | ✅ | | | +| `AES-GCM` | | | ✅ | ✅ | ✅ | | | +| `AES-KW` | | | ✅ | ✅ | ✅ | | | +| `AES-OCB` | | | ❌ | | ❌ | | | +| `ChaCha20-Poly1305` | | | ❌ | | ❌ | | | +| `ECDH` | ✅ | ✅ | ✅ | ✅ | | ✅ | | +| `ECDSA` | ✅ | ✅ | ✅ | ✅ | | ✅ | | +| `Ed25519` | ✅ | ✅ | ❌ | ❌ | | ❌ | | +| `Ed448` | ✅ | ✅ | ❌ | ❌ | | ❌ | | +| `HKDF` | | | | ❌ | ❌ | | | +| `HMAC` | | | ✅ | ✅ | ✅ | | | +| `ML-DSA-44` | ✅ | ✅ | ✅ | | | ✅ | ✅ | +| `ML-DSA-65` | ✅ | ✅ | ✅ | | | ✅ | ✅ | +| `ML-DSA-87` | ✅ | ✅ | ✅ | | | ✅ | ✅ | +| `ML-KEM-512` | ❌ | ❌ | | | | ❌ | ❌ | +| `ML-KEM-768` | ❌ | ❌ | | | | ❌ | ❌ | +| `ML-KEM-1024` | ❌ | ❌ | | | | ❌ | ❌ | +| `PBKDF2` | | | | ✅ | ✅ | | | +| `RSA-OAEP` | ✅ | ✅ | ✅ | | | | | +| `RSA-PSS` | ✅ | ✅ | ✅ | | | | | +| `RSASSA-PKCS1-v1_5` | ✅ | ✅ | ✅ | | | | | +| `X25519` | ❌ | ❌ | ❌ | ❌ | | ❌ | | +| `X448` | ❌ | ❌ | ❌ | ❌ | | ❌ | | ## `subtle.sign` | Algorithm | Status | | --------- | :----: | | `ECDSA` | ✅ | -| `Ed25519` | ❌ | -| `Ed448` | ❌ | -| `HMAC` | ❌ | -| `ML-DSA-44` | ❌ | -| `ML-DSA-65` | ❌ | -| `ML-DSA-87` | ❌ | -| `RSA-PSS` | ❌ | -| `RSASSA-PKCS1-v1_5` | ❌ | +| `Ed25519` | ✅ | +| `Ed448` | ✅ | +| `HMAC` | ✅ | +| `ML-DSA-44` | ✅ | +| `ML-DSA-65` | ✅ | +| `ML-DSA-87` | ✅ | +| `RSA-PSS` | ✅ | +| `RSASSA-PKCS1-v1_5` | ✅ | ## `subtle.unwrapKey` @@ -453,14 +457,14 @@ This document attempts to describe the implementation status of Crypto APIs/Inte | Algorithm | Status | | --------- | :----: | | `ECDSA` | ✅ | -| `Ed25519` | ❌ | -| `Ed448` | ❌ | -| `HMAC` | ❌ | -| `ML-DSA-44` | ❌ | -| `ML-DSA-65` | ❌ | -| `ML-DSA-87` | ❌ | -| `RSA-PSS` | ❌ | -| `RSASSA-PKCS1-v1_5` | ❌ | +| `Ed25519` | ✅ | +| `Ed448` | ✅ | +| `HMAC` | ✅ | +| `ML-DSA-44` | ✅ | +| `ML-DSA-65` | ✅ | +| `ML-DSA-87` | ✅ | +| `RSA-PSS` | ✅ | +| `RSASSA-PKCS1-v1_5` | ✅ | ## `subtle.wrapKey` diff --git a/example/ios/Podfile b/example/ios/Podfile index 74f2ca20..5495dd75 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -40,16 +40,16 @@ target 'QuickCryptoExample' do target.build_configurations.each do |config| config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.1' config.build_settings['CLANG_CXX_LANGUAGE_STANDARD'] = 'c++20' - + # Force C++20 for all targets, especially problematic ones config.build_settings['GCC_C_LANGUAGE_STANDARD'] = 'gnu11' config.build_settings['CLANG_CXX_LIBRARY'] = 'libc++' - + # Remove any conflicting C++ standard flags config.build_settings.delete('CLANG_CXX_LANGUAGE_STANDARD_OVERRIDE') end end - + # Specifically target RCT-Folly and other React Native core pods installer.pods_project.targets.each do |target| if target.name.include?('Folly') || target.name.include?('React-') || target.name.include?('RCT') @@ -58,5 +58,48 @@ target 'QuickCryptoExample' do end end end + + # Embed OpenSSL.framework from SPM into the app bundle + # SPM frameworks added to Pods project need manual embedding + main_project_path = File.join(installer.sandbox.root.parent, 'QuickCryptoExample.xcodeproj') + main_project = Xcodeproj::Project.open(main_project_path) + app_target = main_project.targets.find { |t| t.name == 'QuickCryptoExample' } + + if app_target + embed_phase_name = 'Embed SPM Frameworks (OpenSSL)' + existing_phase = app_target.shell_script_build_phases.find { |p| p.name == embed_phase_name } + + unless existing_phase + phase = app_target.new_shell_script_build_phase(embed_phase_name) + phase.shell_script = <<~SCRIPT + # Embed OpenSSL.framework from SPM build into app bundle + # SPM builds the framework to BUILT_PRODUCTS_DIR but doesn't embed it + OPENSSL_FRAMEWORK="${BUILT_PRODUCTS_DIR}/OpenSSL.framework" + + if [ -d "$OPENSSL_FRAMEWORK" ]; then + echo "Found OpenSSL.framework at $OPENSSL_FRAMEWORK" + mkdir -p "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}" + rsync -av --delete "$OPENSSL_FRAMEWORK" "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/" + + # Code sign if required + if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" ] && [ "${CODE_SIGNING_REQUIRED:-}" != "NO" ]; then + /usr/bin/codesign --force --sign "${EXPANDED_CODE_SIGN_IDENTITY}" --preserve-metadata=identifier,entitlements "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework" + fi + echo "OpenSSL.framework embedded successfully" + else + echo "warning: OpenSSL.framework not found at $OPENSSL_FRAMEWORK" + fi + SCRIPT + + # Move it before the existing embed frameworks phase + embed_pods_phase = app_target.shell_script_build_phases.find { |p| p.name == '[CP] Embed Pods Frameworks' } + if embed_pods_phase + app_target.build_phases.move(phase, app_target.build_phases.index(embed_pods_phase)) + end + + main_project.save + Pod::UI.puts "[QuickCrypto] Added 'Embed SPM Frameworks (OpenSSL)' build phase" + end + end end end diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index d106c53a..cb482974 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -37,7 +37,6 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - OpenSSL-Universal (3.3.3001) - QuickCrypto (1.0.0): - boost - DoubleConversion @@ -46,7 +45,6 @@ PODS: - glog - hermes-engine - NitroModules - - OpenSSL-Universal (= 3.3.3001) - RCT-Folly - RCT-Folly/Fabric - RCTRequired @@ -2605,7 +2603,6 @@ DEPENDENCIES: SPEC REPOS: trunk: - - OpenSSL-Universal - SocketRocket EXTERNAL SOURCES: @@ -2776,8 +2773,7 @@ SPEC CHECKSUMS: glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 hermes-engine: 4f8246b1f6d79f625e0d99472d1f3a71da4d28ca NitroModules: 1715fe0e22defd9e2cdd48fb5e0dbfd01af54bec - OpenSSL-Universal: 6082b0bf950e5636fe0d78def171184e2b3899c2 - QuickCrypto: 4e82c6565ea7b5f9d4c3f0ad3f19b785a676b4cc + QuickCrypto: fad28b0727d1b6ffecab0fc8e407c14b135cdca0 RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 RCTDeprecation: c4b9e2fd0ab200e3af72b013ed6113187c607077 RCTRequired: e97dd5dafc1db8094e63bc5031e0371f092ae92a @@ -2849,6 +2845,6 @@ SPEC CHECKSUMS: SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: 11c9686a21e2cd82a094a723649d9f4507200fb0 -PODFILE CHECKSUM: 8bf59f4e86b38489f786b2878e119cdf1824ca75 +PODFILE CHECKSUM: bc958092bb9060694d04c6fcf716262b0549cded COCOAPODS: 1.15.2 diff --git a/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj b/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj index a2d10833..bcfe455e 100644 --- a/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj +++ b/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj @@ -114,6 +114,7 @@ 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + 8C63FDE6D14F20AAC744357A /* Embed SPM Frameworks (OpenSSL) */, 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */, E235C05ADACE081382539298 /* [CP] Copy Pods Resources */, ); @@ -204,6 +205,24 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-QuickCryptoExample/Pods-QuickCryptoExample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; + 8C63FDE6D14F20AAC744357A /* Embed SPM Frameworks (OpenSSL) */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Embed SPM Frameworks (OpenSSL)"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Embed OpenSSL.framework from SPM build into app bundle\n# SPM builds the framework to BUILT_PRODUCTS_DIR but doesn't embed it\nOPENSSL_FRAMEWORK=\"${BUILT_PRODUCTS_DIR}/OpenSSL.framework\"\n\nif [ -d \"$OPENSSL_FRAMEWORK\" ]; then\n echo \"Found OpenSSL.framework at $OPENSSL_FRAMEWORK\"\n mkdir -p \"${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}\"\n rsync -av --delete \"$OPENSSL_FRAMEWORK\" \"${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/\"\n\n # Code sign if required\n if [ -n \"${EXPANDED_CODE_SIGN_IDENTITY:-}\" ] && [ \"${CODE_SIGNING_REQUIRED:-}\" != \"NO\" ]; then\n /usr/bin/codesign --force --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" --preserve-metadata=identifier,entitlements \"${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework\"\n fi\n echo \"OpenSSL.framework embedded successfully\"\nelse\n echo \"warning: OpenSSL.framework not found at $OPENSSL_FRAMEWORK\"\nfi\n"; + }; C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/example/ios/QuickCryptoExample.xcworkspace/xcshareddata/swiftpm/Package.resolved b/example/ios/QuickCryptoExample.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..143f9da7 --- /dev/null +++ b/example/ios/QuickCryptoExample.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "aaa2c47e8981f1f40215b85209dbcc3180d197b2d32bdec06d6be814b55ccb42", + "pins" : [ + { + "identity" : "openssl", + "kind" : "remoteSourceControl", + "location" : "https://github.com/krzyzanowskim/OpenSSL.git", + "state" : { + "revision" : "e7d34385da795aaaeb2212e38c7401e5c333f792", + "version" : "3.6.1" + } + } + ], + "version" : 3 +} diff --git a/example/src/hooks/useTestsList.ts b/example/src/hooks/useTestsList.ts index b9a6ff00..b7b4e347 100644 --- a/example/src/hooks/useTestsList.ts +++ b/example/src/hooks/useTestsList.ts @@ -12,11 +12,11 @@ import '../tests/constants/constants_tests'; import '../tests/hash/hash_tests'; import '../tests/hmac/hmac_tests'; import '../tests/jose/jose'; -import '../tests/keys/sign_verify_streaming'; -import '../tests/keys/public_cipher'; import '../tests/keys/create_keys'; import '../tests/keys/generate_key'; import '../tests/keys/generate_keypair'; +import '../tests/keys/public_cipher'; +import '../tests/keys/sign_verify_streaming'; import '../tests/pbkdf2/pbkdf2_tests'; import '../tests/random/random_tests'; import '../tests/subtle/deriveBits'; diff --git a/example/src/tests/subtle/generateKey.ts b/example/src/tests/subtle/generateKey.ts index f705936a..cf9b23e2 100644 --- a/example/src/tests/subtle/generateKey.ts +++ b/example/src/tests/subtle/generateKey.ts @@ -413,6 +413,68 @@ testRSAKeyGen( ['encrypt', 'wrapKey'], ); +// --- ML-DSA Key Generation Tests --- + +type MlDsaVariant = 'ML-DSA-44' | 'ML-DSA-65' | 'ML-DSA-87'; +const MLDSA_VARIANTS: MlDsaVariant[] = ['ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']; + +interface TestCryptoKeyPairMlDsa { + publicKey: CryptoKey; + privateKey: CryptoKey; +} + +for (const variant of MLDSA_VARIANTS) { + test(SUITE, `ML-DSA keygen: ${variant}`, async () => { + const keyPair = await subtle.generateKey({ name: variant }, true, [ + 'sign', + 'verify', + ]); + + const { publicKey, privateKey } = keyPair as TestCryptoKeyPairMlDsa; + + expect(publicKey !== undefined); + expect(privateKey !== undefined); + expect(publicKey instanceof Object); + expect(privateKey instanceof Object); + expect(publicKey.type).to.equal('public'); + expect(privateKey.type).to.equal('private'); + expect(publicKey.extractable).to.equal(true); + expect(privateKey.extractable).to.equal(true); + expect(publicKey.usages).to.deep.equal(['verify']); + expect(privateKey.usages).to.deep.equal(['sign']); + expect(publicKey.algorithm.name).to.equal(variant); + expect(privateKey.algorithm.name).to.equal(variant); + }); + + test(SUITE, `ML-DSA keygen non-extractable: ${variant}`, async () => { + const keyPair = await subtle.generateKey({ name: variant }, false, [ + 'sign', + 'verify', + ]); + + const { publicKey, privateKey } = keyPair as TestCryptoKeyPairMlDsa; + + // Public key is always extractable + expect(publicKey.extractable).to.equal(true); + // Private key respects extractable parameter + expect(privateKey.extractable).to.equal(false); + }); +} + +// Test bad usages for ML-DSA +test(SUITE, 'ML-DSA bad usages', async () => { + await assertThrowsAsync( + async () => await subtle.generateKey({ name: 'ML-DSA-44' }, true, []), + 'Usages cannot be empty', + ); + + await assertThrowsAsync( + async () => + await subtle.generateKey({ name: 'ML-DSA-44' }, true, ['encrypt']), + 'Unsupported key usage', + ); +}); + /* // Test AES Key Generation type AESArgs = [AESAlgorithm, AESLength, KeyUsage[]]; diff --git a/example/src/tests/subtle/import_export.ts b/example/src/tests/subtle/import_export.ts index 19eb6a0f..0c5fb678 100644 --- a/example/src/tests/subtle/import_export.ts +++ b/example/src/tests/subtle/import_export.ts @@ -2056,3 +2056,134 @@ test(SUITE, 'RSASSA-PKCS1-v1_5 jwk', async () => { // ); // } // } + +// --- ML-DSA Import/Export Tests --- + +type MlDsaVariant = 'ML-DSA-44' | 'ML-DSA-65' | 'ML-DSA-87'; +const MLDSA_VARIANTS: MlDsaVariant[] = ['ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']; + +for (const variant of MLDSA_VARIANTS) { + test(SUITE, `${variant} spki export/import`, async () => { + const generated = await subtle.generateKey({ name: variant }, true, [ + 'sign', + 'verify', + ]); + const { publicKey } = generated as CryptoKeyPair; + + const exported = await subtle.exportKey('spki', publicKey as CryptoKey); + expect(exported).to.be.instanceOf(ArrayBuffer); + expect((exported as ArrayBuffer).byteLength).to.be.greaterThan(0); + + const imported = await subtle.importKey( + 'spki', + exported, + { name: variant }, + true, + ['verify'], + ); + expect(imported.type).to.equal('public'); + expect(imported.algorithm.name).to.equal(variant); + expect(imported.usages).to.deep.equal(['verify']); + expect(imported.extractable).to.equal(true); + }); + + test(SUITE, `${variant} pkcs8 export/import`, async () => { + const generated = await subtle.generateKey({ name: variant }, true, [ + 'sign', + 'verify', + ]); + const { privateKey } = generated as CryptoKeyPair; + + const exported = await subtle.exportKey('pkcs8', privateKey as CryptoKey); + expect(exported).to.be.instanceOf(ArrayBuffer); + expect((exported as ArrayBuffer).byteLength).to.be.greaterThan(0); + + const imported = await subtle.importKey( + 'pkcs8', + exported, + { name: variant }, + true, + ['sign'], + ); + expect(imported.type).to.equal('private'); + expect(imported.algorithm.name).to.equal(variant); + expect(imported.usages).to.deep.equal(['sign']); + expect(imported.extractable).to.equal(true); + }); + + test(SUITE, `${variant} round-trip with sign/verify`, async () => { + const testData = new TextEncoder().encode('ML-DSA import/export test'); + + const generated = await subtle.generateKey({ name: variant }, true, [ + 'sign', + 'verify', + ]); + const { publicKey, privateKey } = generated as CryptoKeyPair; + + // Export keys + const exportedPublic = await subtle.exportKey( + 'spki', + publicKey as CryptoKey, + ); + const exportedPrivate = await subtle.exportKey( + 'pkcs8', + privateKey as CryptoKey, + ); + + // Import keys + const importedPublic = await subtle.importKey( + 'spki', + exportedPublic, + { name: variant }, + true, + ['verify'], + ); + const importedPrivate = await subtle.importKey( + 'pkcs8', + exportedPrivate, + { name: variant }, + true, + ['sign'], + ); + + // Sign with imported private key + const signature = await subtle.sign( + { name: variant }, + importedPrivate, + testData, + ); + + // Verify with imported public key + const isValid = await subtle.verify( + { name: variant }, + importedPublic, + signature, + testData, + ); + expect(isValid).to.equal(true); + + // Also verify with original public key + const isValidOriginal = await subtle.verify( + { name: variant }, + publicKey as CryptoKey, + signature, + testData, + ); + expect(isValidOriginal).to.equal(true); + }); +} + +// ML-DSA error handling tests +test(SUITE, 'ML-DSA-44 importKey rejects invalid format', async () => { + await assertThrowsAsync( + async () => + await subtle.importKey( + 'raw', + new Uint8Array(32), + { name: 'ML-DSA-44' }, + true, + ['verify'], + ), + 'NotSupportedError', + ); +}); diff --git a/example/src/tests/subtle/sign_verify.ts b/example/src/tests/subtle/sign_verify.ts index fc85733e..a2cfafd6 100644 --- a/example/src/tests/subtle/sign_verify.ts +++ b/example/src/tests/subtle/sign_verify.ts @@ -687,6 +687,119 @@ test(SUITE, 'HMAC verify fails with tampered signature', async () => { // --- Key Import/Export and Sign/Verify --- +// --- ML-DSA Tests --- + +type MlDsaVariant = 'ML-DSA-44' | 'ML-DSA-65' | 'ML-DSA-87'; +const MLDSA_VARIANTS: MlDsaVariant[] = ['ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']; +const MLDSA_SIGNATURE_SIZES: Record = { + 'ML-DSA-44': 2420, + 'ML-DSA-65': 3309, + 'ML-DSA-87': 4627, +}; + +for (const variant of MLDSA_VARIANTS) { + test(SUITE, `${variant} sign/verify`, async () => { + const keyPair = await generateKeyPairChecked({ name: variant }, true, [ + 'sign', + 'verify', + ]); + + const signature = await subtle.sign( + { name: variant }, + keyPair.privateKey, + testData, + ); + + expect(signature.byteLength).to.equal(MLDSA_SIGNATURE_SIZES[variant]); + + const isValid = await subtle.verify( + { name: variant }, + keyPair.publicKey, + signature, + testData, + ); + + expect(isValid).to.equal(true); + }); + + test(SUITE, `${variant} sign/verify with empty data`, async () => { + const keyPair = await generateKeyPairChecked({ name: variant }, true, [ + 'sign', + 'verify', + ]); + + const signature = await subtle.sign( + { name: variant }, + keyPair.privateKey, + emptyData, + ); + + expect(signature.byteLength).to.equal(MLDSA_SIGNATURE_SIZES[variant]); + + const isValid = await subtle.verify( + { name: variant }, + keyPair.publicKey, + signature, + emptyData, + ); + + expect(isValid).to.equal(true); + }); + + test(SUITE, `${variant} verify fails with tampered signature`, async () => { + const keyPair = await generateKeyPairChecked({ name: variant }, true, [ + 'sign', + 'verify', + ]); + + const signature = await subtle.sign( + { name: variant }, + keyPair.privateKey, + testData, + ); + + const tamperedSig = new Uint8Array(signature); + tamperedSig[0] = (tamperedSig[0] ?? 0) ^ 0xff; + + const isValid = await subtle.verify( + { name: variant }, + keyPair.publicKey, + tamperedSig, + testData, + ); + + expect(isValid).to.equal(false); + }); + + test(SUITE, `${variant} verify fails with wrong public key`, async () => { + const keyPair1 = await generateKeyPairChecked({ name: variant }, true, [ + 'sign', + 'verify', + ]); + const keyPair2 = await generateKeyPairChecked({ name: variant }, true, [ + 'sign', + 'verify', + ]); + + const signature = await subtle.sign( + { name: variant }, + keyPair1.privateKey, + testData, + ); + + const isValid = await subtle.verify( + { name: variant }, + keyPair2.publicKey, + signature, + testData, + ); + + expect(isValid).to.equal(false); + }); +} + +// --- Key Import/Export and Sign/Verify --- + test(SUITE, 'Sign with imported Ed25519 key', async () => { const keyPair = await generateKeyPairChecked({ name: 'Ed25519' }, true, [ 'sign', diff --git a/packages/react-native-quick-crypto/QuickCrypto.podspec b/packages/react-native-quick-crypto/QuickCrypto.podspec index 61b8074b..561bba1c 100644 --- a/packages/react-native-quick-crypto/QuickCrypto.podspec +++ b/packages/react-native-quick-crypto/QuickCrypto.podspec @@ -92,10 +92,12 @@ Pod::Spec.new do |s| "ios/**/*.{h,m,mm}", # implementation (C++) "cpp/**/*.{hpp,cpp}", - # dependencies (C++) - "deps/**/*.{h,cc}", + # dependencies (C++) - ncrypto + "deps/ncrypto/include/*.{h}", + "deps/ncrypto/src/*.{cpp}", # dependencies (C) - exclude BLAKE3 x86 SIMD files (only use portable + NEON for ARM) - "deps/**/*.{h,c}", + "deps/blake3/c/*.{h,c}", + "deps/fastpbkdf2/*.{h,c}", ] # Exclude BLAKE3 x86-specific SIMD implementations (SSE2, SSE4.1, AVX2, AVX-512) @@ -148,7 +150,7 @@ Pod::Spec.new do |s| # Add cpp subdirectories to header search paths cpp_headers = [ "\"$(PODS_TARGET_SRCROOT)/cpp/utils\"", - "\"$(PODS_TARGET_SRCROOT)/deps/ncrypto\"", + "\"$(PODS_TARGET_SRCROOT)/deps/ncrypto/include\"", "\"$(PODS_TARGET_SRCROOT)/deps/blake3/c\"", "\"$(PODS_TARGET_SRCROOT)/deps/fastpbkdf2\"" ] @@ -175,6 +177,13 @@ Pod::Spec.new do |s| s.dependency "React-jsi" s.dependency "React-callinvoker" - s.dependency "OpenSSL-Universal", "3.3.3001" + + # OpenSSL 3.6+ via SPM for ML-DSA (post-quantum cryptography) support + spm_dependency(s, + url: 'https://github.com/krzyzanowskim/OpenSSL.git', + requirement: {kind: 'upToNextMajorVersion', minimumVersion: '3.6.0'}, + products: ['OpenSSL'] + ) + install_modules_dependencies(s) end diff --git a/packages/react-native-quick-crypto/android/CMakeLists.txt b/packages/react-native-quick-crypto/android/CMakeLists.txt index 5ec43330..7e1e2f57 100644 --- a/packages/react-native-quick-crypto/android/CMakeLists.txt +++ b/packages/react-native-quick-crypto/android/CMakeLists.txt @@ -40,6 +40,7 @@ add_library( ../cpp/hmac/HybridHmac.cpp ../cpp/keys/HybridKeyObjectHandle.cpp ../cpp/keys/KeyObjectData.cpp + ../cpp/mldsa/HybridMlDsaKeyPair.cpp ../cpp/pbkdf2/HybridPbkdf2.cpp ../cpp/random/HybridRandom.cpp ../cpp/rsa/HybridRsaKeyPair.cpp @@ -47,7 +48,7 @@ add_library( ../cpp/sign/HybridVerifyHandle.cpp ${BLAKE3_SOURCES} ../deps/fastpbkdf2/fastpbkdf2.c - ../deps/ncrypto/ncrypto.cc + ../deps/ncrypto/src/ncrypto.cpp ) # add Nitrogen specs @@ -63,6 +64,7 @@ include_directories( "../cpp/hash" "../cpp/hmac" "../cpp/keys" + "../cpp/mldsa" "../cpp/pbkdf2" "../cpp/random" "../cpp/rsa" @@ -70,7 +72,7 @@ include_directories( "../cpp/utils" "../deps/blake3/c" "../deps/fastpbkdf2" - "../deps/ncrypto" + "../deps/ncrypto/include" ) # Third party libraries (Prefabs) diff --git a/packages/react-native-quick-crypto/android/build.gradle b/packages/react-native-quick-crypto/android/build.gradle index fcfd0ff5..c945982d 100644 --- a/packages/react-native-quick-crypto/android/build.gradle +++ b/packages/react-native-quick-crypto/android/build.gradle @@ -143,7 +143,7 @@ dependencies { implementation project(":react-native-nitro-modules") // Add a dependency on OpenSSL - implementation 'io.github.ronickg:openssl:3.3.2-1' + implementation 'io.github.ronickg:openssl:3.6.0-1' if (sodiumEnabled) { // Add a dependency on libsodium diff --git a/packages/react-native-quick-crypto/cpp/cipher/HybridCipher.cpp b/packages/react-native-quick-crypto/cpp/cipher/HybridCipher.cpp index 588b279a..bc194834 100644 --- a/packages/react-native-quick-crypto/cpp/cipher/HybridCipher.cpp +++ b/packages/react-native-quick-crypto/cpp/cipher/HybridCipher.cpp @@ -297,8 +297,9 @@ void collect_ciphers(EVP_CIPHER* cipher, void* arg) { if (name_str == "NULL" || name_str.find("CTS") != std::string::npos || name_str.find("SIV") != std::string::npos || // Covers -SIV and -GCM-SIV name_str.find("WRAP") != std::string::npos || // Covers -WRAP-INV and -WRAP-PAD-INV - name_str.find("SM4-") != std::string::npos) { - return; // Skip adding this cipher + name_str.find("SM4-") != std::string::npos || + name_str.find("-ETM") != std::string::npos) { // TLS-internal ciphers, not for general use + return; // Skip adding this cipher } // If not filtered out, add it to the list diff --git a/packages/react-native-quick-crypto/cpp/cipher/HybridRsaCipher.cpp b/packages/react-native-quick-crypto/cpp/cipher/HybridRsaCipher.cpp index 1d7c120d..3314e12f 100644 --- a/packages/react-native-quick-crypto/cpp/cipher/HybridRsaCipher.cpp +++ b/packages/react-native-quick-crypto/cpp/cipher/HybridRsaCipher.cpp @@ -320,13 +320,32 @@ std::shared_ptr HybridRsaCipher::privateDecrypt(const std::shared_p throw std::runtime_error("Failed to determine output length: " + std::string(err_buf)); } + if (outlen == 0) { + EVP_PKEY_CTX_free(ctx); + uint8_t* empty_buf = new uint8_t[1]; + return std::make_shared(empty_buf, 0, [empty_buf]() { delete[] empty_buf; }); + } + auto out_buf = std::make_unique(outlen); if (EVP_PKEY_verify_recover(ctx, out_buf.get(), &outlen, in, inlen) <= 0) { - EVP_PKEY_CTX_free(ctx); + // OpenSSL 3.x may return failure when recovering empty plaintext + // In this case outlen is not updated from the initial buffer size + // Check the error and attempt to handle the empty data case unsigned long err = ERR_get_error(); char err_buf[256]; ERR_error_string_n(err, err_buf, sizeof(err_buf)); + + // Check if this is an RSA library error that might indicate empty recovered data + // Error code 0x1C880004 is "RSA lib" error from OpenSSL 3.x provider + if ((err & 0xFFFFFFF) == 0x1C880004 || (err & 0xFF) == 0x04) { + ERR_clear_error(); + EVP_PKEY_CTX_free(ctx); + uint8_t* empty_buf = new uint8_t[1]; + return std::make_shared(empty_buf, 0, [empty_buf]() { delete[] empty_buf; }); + } + + EVP_PKEY_CTX_free(ctx); throw std::runtime_error("Private decryption failed: " + std::string(err_buf)); } diff --git a/packages/react-native-quick-crypto/cpp/keys/HybridKeyObjectHandle.cpp b/packages/react-native-quick-crypto/cpp/keys/HybridKeyObjectHandle.cpp index 0820f79b..6498246d 100644 --- a/packages/react-native-quick-crypto/cpp/keys/HybridKeyObjectHandle.cpp +++ b/packages/react-native-quick-crypto/cpp/keys/HybridKeyObjectHandle.cpp @@ -322,6 +322,14 @@ AsymmetricKeyType HybridKeyObjectHandle::getAsymmetricKeyType() { return AsymmetricKeyType::ED25519; case EVP_PKEY_ED448: return AsymmetricKeyType::ED448; +#if OPENSSL_VERSION_NUMBER >= 0x30500000L + case EVP_PKEY_ML_DSA_44: + return AsymmetricKeyType::ML_DSA_44; + case EVP_PKEY_ML_DSA_65: + return AsymmetricKeyType::ML_DSA_65; + case EVP_PKEY_ML_DSA_87: + return AsymmetricKeyType::ML_DSA_87; +#endif default: throw std::runtime_error("Unsupported asymmetric key type"); } diff --git a/packages/react-native-quick-crypto/cpp/keys/KeyObjectData.hpp b/packages/react-native-quick-crypto/cpp/keys/KeyObjectData.hpp index c5d048a2..392d8a53 100644 --- a/packages/react-native-quick-crypto/cpp/keys/KeyObjectData.hpp +++ b/packages/react-native-quick-crypto/cpp/keys/KeyObjectData.hpp @@ -2,11 +2,11 @@ #include -#include "../../deps/ncrypto/ncrypto.h" #include "KFormatType.hpp" #include "KeyEncoding.hpp" #include "KeyType.hpp" #include "Utils.hpp" +#include namespace margelo::nitro::crypto { diff --git a/packages/react-native-quick-crypto/cpp/mldsa/HybridMlDsaKeyPair.cpp b/packages/react-native-quick-crypto/cpp/mldsa/HybridMlDsaKeyPair.cpp new file mode 100644 index 00000000..ec641127 --- /dev/null +++ b/packages/react-native-quick-crypto/cpp/mldsa/HybridMlDsaKeyPair.cpp @@ -0,0 +1,264 @@ +#include "HybridMlDsaKeyPair.hpp" + +#include +#include +#include +#include + +#include "Utils.hpp" + +#if OPENSSL_VERSION_NUMBER >= 0x30500000L +#define RNQC_HAS_ML_DSA 1 +#else +#define RNQC_HAS_ML_DSA 0 +#endif + +namespace margelo::nitro::crypto { + +HybridMlDsaKeyPair::~HybridMlDsaKeyPair() { + if (pkey_ != nullptr) { + EVP_PKEY_free(pkey_); + pkey_ = nullptr; + } +} + +int HybridMlDsaKeyPair::getEvpPkeyType() const { +#if RNQC_HAS_ML_DSA + if (variant_ == "ML-DSA-44") + return EVP_PKEY_ML_DSA_44; + if (variant_ == "ML-DSA-65") + return EVP_PKEY_ML_DSA_65; + if (variant_ == "ML-DSA-87") + return EVP_PKEY_ML_DSA_87; +#endif + return 0; +} + +void HybridMlDsaKeyPair::setVariant(const std::string& variant) { +#if !RNQC_HAS_ML_DSA + throw std::runtime_error("ML-DSA requires OpenSSL 3.5+"); +#endif + if (variant != "ML-DSA-44" && variant != "ML-DSA-65" && variant != "ML-DSA-87") { + throw std::runtime_error("Invalid ML-DSA variant: " + variant + ". Must be ML-DSA-44, ML-DSA-65, or ML-DSA-87"); + } + variant_ = variant; +} + +std::shared_ptr> HybridMlDsaKeyPair::generateKeyPair(double publicFormat, double publicType, double privateFormat, + double privateType) { + return Promise::async([this, publicFormat, publicType, privateFormat, privateType]() { + this->generateKeyPairSync(publicFormat, publicType, privateFormat, privateType); + }); +} + +void HybridMlDsaKeyPair::generateKeyPairSync(double publicFormat, double publicType, double privateFormat, double privateType) { +#if !RNQC_HAS_ML_DSA + throw std::runtime_error("ML-DSA requires OpenSSL 3.5+"); +#else + clearOpenSSLErrors(); + + if (variant_.empty()) { + throw std::runtime_error("ML-DSA variant not set. Call setVariant() first."); + } + + publicFormat_ = static_cast(publicFormat); + publicType_ = static_cast(publicType); + privateFormat_ = static_cast(privateFormat); + privateType_ = static_cast(privateType); + + if (pkey_ != nullptr) { + EVP_PKEY_free(pkey_); + pkey_ = nullptr; + } + + EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_from_name(nullptr, variant_.c_str(), nullptr); + if (pctx == nullptr) { + throw std::runtime_error("Failed to create key context for " + variant_ + ": " + getOpenSSLError()); + } + + if (EVP_PKEY_keygen_init(pctx) <= 0) { + EVP_PKEY_CTX_free(pctx); + throw std::runtime_error("Failed to initialize keygen: " + getOpenSSLError()); + } + + if (EVP_PKEY_keygen(pctx, &pkey_) <= 0) { + EVP_PKEY_CTX_free(pctx); + throw std::runtime_error("Failed to generate ML-DSA key pair: " + getOpenSSLError()); + } + + EVP_PKEY_CTX_free(pctx); +#endif +} + +std::shared_ptr HybridMlDsaKeyPair::getPublicKey() { +#if !RNQC_HAS_ML_DSA + throw std::runtime_error("ML-DSA requires OpenSSL 3.5+"); +#else + checkKeyPair(); + + BIO* bio = BIO_new(BIO_s_mem()); + if (!bio) { + throw std::runtime_error("Failed to create BIO for public key export"); + } + + int result; + if (publicFormat_ == 1) { + result = PEM_write_bio_PUBKEY(bio, pkey_); + } else { + result = i2d_PUBKEY_bio(bio, pkey_); + } + + if (result != 1) { + BIO_free(bio); + throw std::runtime_error("Failed to export public key: " + getOpenSSLError()); + } + + BUF_MEM* bptr; + BIO_get_mem_ptr(bio, &bptr); + + uint8_t* data = new uint8_t[bptr->length]; + memcpy(data, bptr->data, bptr->length); + size_t len = bptr->length; + + BIO_free(bio); + + return std::make_shared(data, len, [=]() { delete[] data; }); +#endif +} + +std::shared_ptr HybridMlDsaKeyPair::getPrivateKey() { +#if !RNQC_HAS_ML_DSA + throw std::runtime_error("ML-DSA requires OpenSSL 3.5+"); +#else + checkKeyPair(); + + BIO* bio = BIO_new(BIO_s_mem()); + if (!bio) { + throw std::runtime_error("Failed to create BIO for private key export"); + } + + int result; + if (privateFormat_ == 1) { + result = PEM_write_bio_PrivateKey(bio, pkey_, nullptr, nullptr, 0, nullptr, nullptr); + } else { + // Use PKCS8 format for DER export (not raw private key format) + result = i2d_PKCS8PrivateKey_bio(bio, pkey_, nullptr, nullptr, 0, nullptr, nullptr); + } + + if (result != 1) { + BIO_free(bio); + throw std::runtime_error("Failed to export private key: " + getOpenSSLError()); + } + + BUF_MEM* bptr; + BIO_get_mem_ptr(bio, &bptr); + + uint8_t* data = new uint8_t[bptr->length]; + memcpy(data, bptr->data, bptr->length); + size_t len = bptr->length; + + BIO_free(bio); + + return std::make_shared(data, len, [=]() { delete[] data; }); +#endif +} + +std::shared_ptr>> HybridMlDsaKeyPair::sign(const std::shared_ptr& message) { + auto nativeMessage = ToNativeArrayBuffer(message); + return Promise>::async([this, nativeMessage]() { return this->signSync(nativeMessage); }); +} + +std::shared_ptr HybridMlDsaKeyPair::signSync(const std::shared_ptr& message) { +#if !RNQC_HAS_ML_DSA + throw std::runtime_error("ML-DSA requires OpenSSL 3.5+"); +#else + clearOpenSSLErrors(); + checkKeyPair(); + + EVP_MD_CTX* md_ctx = EVP_MD_CTX_new(); + if (md_ctx == nullptr) { + throw std::runtime_error("Failed to create signing context"); + } + + EVP_PKEY_CTX* pkey_ctx = EVP_PKEY_CTX_new_from_name(nullptr, variant_.c_str(), nullptr); + if (pkey_ctx == nullptr) { + EVP_MD_CTX_free(md_ctx); + throw std::runtime_error("Failed to create signing context for " + variant_); + } + + if (EVP_DigestSignInit(md_ctx, &pkey_ctx, nullptr, nullptr, pkey_) <= 0) { + EVP_MD_CTX_free(md_ctx); + EVP_PKEY_CTX_free(pkey_ctx); + throw std::runtime_error("Failed to initialize signing: " + getOpenSSLError()); + } + + size_t sig_len = 0; + if (EVP_DigestSign(md_ctx, nullptr, &sig_len, message->data(), message->size()) <= 0) { + EVP_MD_CTX_free(md_ctx); + throw std::runtime_error("Failed to calculate signature size: " + getOpenSSLError()); + } + + uint8_t* sig = new uint8_t[sig_len]; + + if (EVP_DigestSign(md_ctx, sig, &sig_len, message->data(), message->size()) <= 0) { + EVP_MD_CTX_free(md_ctx); + delete[] sig; + throw std::runtime_error("Failed to sign message: " + getOpenSSLError()); + } + + EVP_MD_CTX_free(md_ctx); + + return std::make_shared(sig, sig_len, [=]() { delete[] sig; }); +#endif +} + +std::shared_ptr> HybridMlDsaKeyPair::verify(const std::shared_ptr& signature, + const std::shared_ptr& message) { + auto nativeSignature = ToNativeArrayBuffer(signature); + auto nativeMessage = ToNativeArrayBuffer(message); + return Promise::async([this, nativeSignature, nativeMessage]() { return this->verifySync(nativeSignature, nativeMessage); }); +} + +bool HybridMlDsaKeyPair::verifySync(const std::shared_ptr& signature, const std::shared_ptr& message) { +#if !RNQC_HAS_ML_DSA + throw std::runtime_error("ML-DSA requires OpenSSL 3.5+"); +#else + clearOpenSSLErrors(); + checkKeyPair(); + + EVP_MD_CTX* md_ctx = EVP_MD_CTX_new(); + if (md_ctx == nullptr) { + throw std::runtime_error("Failed to create verify context"); + } + + EVP_PKEY_CTX* pkey_ctx = EVP_PKEY_CTX_new_from_name(nullptr, variant_.c_str(), nullptr); + if (pkey_ctx == nullptr) { + EVP_MD_CTX_free(md_ctx); + throw std::runtime_error("Failed to create verify context for " + variant_); + } + + if (EVP_DigestVerifyInit(md_ctx, &pkey_ctx, nullptr, nullptr, pkey_) <= 0) { + EVP_MD_CTX_free(md_ctx); + EVP_PKEY_CTX_free(pkey_ctx); + throw std::runtime_error("Failed to initialize verification: " + getOpenSSLError()); + } + + int result = EVP_DigestVerify(md_ctx, signature->data(), signature->size(), message->data(), message->size()); + + EVP_MD_CTX_free(md_ctx); + + if (result < 0) { + throw std::runtime_error("Verification error: " + getOpenSSLError()); + } + + return result == 1; +#endif +} + +void HybridMlDsaKeyPair::checkKeyPair() { + if (pkey_ == nullptr) { + throw std::runtime_error("Key pair not initialized"); + } +} + +} // namespace margelo::nitro::crypto diff --git a/packages/react-native-quick-crypto/cpp/mldsa/HybridMlDsaKeyPair.hpp b/packages/react-native-quick-crypto/cpp/mldsa/HybridMlDsaKeyPair.hpp new file mode 100644 index 00000000..e4b95171 --- /dev/null +++ b/packages/react-native-quick-crypto/cpp/mldsa/HybridMlDsaKeyPair.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +#include "HybridMlDsaKeyPairSpec.hpp" + +namespace margelo::nitro::crypto { + +class HybridMlDsaKeyPair : public HybridMlDsaKeyPairSpec { + public: + HybridMlDsaKeyPair() : HybridObject(TAG) {} + ~HybridMlDsaKeyPair(); + + std::shared_ptr> generateKeyPair(double publicFormat, double publicType, double privateFormat, double privateType) override; + + void generateKeyPairSync(double publicFormat, double publicType, double privateFormat, double privateType) override; + + std::shared_ptr getPublicKey() override; + std::shared_ptr getPrivateKey() override; + + std::shared_ptr>> sign(const std::shared_ptr& message) override; + + std::shared_ptr signSync(const std::shared_ptr& message) override; + + std::shared_ptr> verify(const std::shared_ptr& signature, + const std::shared_ptr& message) override; + + bool verifySync(const std::shared_ptr& signature, const std::shared_ptr& message) override; + + void setVariant(const std::string& variant) override; + + private: + std::string variant_; + EVP_PKEY* pkey_ = nullptr; + + int publicFormat_ = -1; + int publicType_ = -1; + int privateFormat_ = -1; + int privateType_ = -1; + + void checkKeyPair(); + int getEvpPkeyType() const; +}; + +} // namespace margelo::nitro::crypto diff --git a/packages/react-native-quick-crypto/cpp/sign/HybridSignHandle.cpp b/packages/react-native-quick-crypto/cpp/sign/HybridSignHandle.cpp index 3a7aec31..c81a4a4e 100644 --- a/packages/react-native-quick-crypto/cpp/sign/HybridSignHandle.cpp +++ b/packages/react-native-quick-crypto/cpp/sign/HybridSignHandle.cpp @@ -9,6 +9,12 @@ #include #include +#if OPENSSL_VERSION_NUMBER >= 0x30500000L +#define RNQC_HAS_ML_DSA 1 +#else +#define RNQC_HAS_ML_DSA 0 +#endif + namespace margelo::nitro::crypto { using margelo::nitro::NativeArrayBuffer; @@ -22,17 +28,28 @@ HybridSignHandle::~HybridSignHandle() { void HybridSignHandle::init(const std::string& algorithm) { algorithm_name = algorithm; - md = getDigestByName(algorithm); - md_ctx = EVP_MD_CTX_new(); - if (!md_ctx) { - throw std::runtime_error("Failed to create message digest context"); - } + // For ML-DSA and other pure signature schemes, algorithm may be empty/null + if (!algorithm.empty()) { + md = getDigestByName(algorithm); - if (EVP_DigestInit_ex(md_ctx, md, nullptr) <= 0) { - EVP_MD_CTX_free(md_ctx); - md_ctx = nullptr; - throw std::runtime_error("Failed to initialize message digest"); + md_ctx = EVP_MD_CTX_new(); + if (!md_ctx) { + throw std::runtime_error("Failed to create message digest context"); + } + + if (EVP_DigestInit_ex(md_ctx, md, nullptr) <= 0) { + EVP_MD_CTX_free(md_ctx); + md_ctx = nullptr; + throw std::runtime_error("Failed to initialize message digest"); + } + } else { + // No digest for pure signature schemes like ML-DSA + md = nullptr; + md_ctx = EVP_MD_CTX_new(); + if (!md_ctx) { + throw std::runtime_error("Failed to create message digest context"); + } } } @@ -43,22 +60,60 @@ void HybridSignHandle::update(const std::shared_ptr& data) { auto native_data = ToNativeArrayBuffer(data); - // Accumulate raw data for potential one-shot signing (Ed25519/Ed448) + // Accumulate raw data for potential one-shot signing (Ed25519/Ed448/ML-DSA) const uint8_t* ptr = reinterpret_cast(native_data->data()); data_buffer.insert(data_buffer.end(), ptr, ptr + native_data->size()); - if (EVP_DigestUpdate(md_ctx, native_data->data(), native_data->size()) <= 0) { - unsigned long err = ERR_get_error(); - char err_buf[256]; - ERR_error_string_n(err, err_buf, sizeof(err_buf)); - throw std::runtime_error("Failed to update digest: " + std::string(err_buf)); + // Only update digest if we have one (not needed for pure signature schemes) + if (md != nullptr) { + if (EVP_DigestUpdate(md_ctx, native_data->data(), native_data->size()) <= 0) { + unsigned long err = ERR_get_error(); + char err_buf[256]; + ERR_error_string_n(err, err_buf, sizeof(err_buf)); + throw std::runtime_error("Failed to update digest: " + std::string(err_buf)); + } } } -// Check if key type requires one-shot signing (Ed25519, Ed448) +// Check if key type requires one-shot signing (Ed25519, Ed448, ML-DSA) static bool isOneShotVariant(EVP_PKEY* pkey) { int type = EVP_PKEY_id(pkey); +#if RNQC_HAS_ML_DSA + return type == EVP_PKEY_ED25519 || type == EVP_PKEY_ED448 || type == EVP_PKEY_ML_DSA_44 || type == EVP_PKEY_ML_DSA_65 || + type == EVP_PKEY_ML_DSA_87; +#else return type == EVP_PKEY_ED25519 || type == EVP_PKEY_ED448; +#endif +} + +// Get the algorithm name for creating PKEY_CTX (for ML-DSA variants) +static const char* getAlgorithmName(EVP_PKEY* pkey) { + int type = EVP_PKEY_id(pkey); +#if RNQC_HAS_ML_DSA + switch (type) { + case EVP_PKEY_ML_DSA_44: + return "ML-DSA-44"; + case EVP_PKEY_ML_DSA_65: + return "ML-DSA-65"; + case EVP_PKEY_ML_DSA_87: + return "ML-DSA-87"; + case EVP_PKEY_ED25519: + return "ED25519"; + case EVP_PKEY_ED448: + return "ED448"; + default: + return nullptr; + } +#else + switch (type) { + case EVP_PKEY_ED25519: + return "ED25519"; + case EVP_PKEY_ED448: + return "ED448"; + default: + return nullptr; + } +#endif } std::shared_ptr HybridSignHandle::sign(const std::shared_ptr& keyHandle, @@ -78,18 +133,35 @@ std::shared_ptr HybridSignHandle::sign(const std::shared_ptr sig_buf; - // Ed25519/Ed448 require one-shot signing with EVP_DigestSign - if (isOneShotVariant(pkey)) { + int pkey_type = EVP_PKEY_id(pkey); + bool is_one_shot = isOneShotVariant(pkey); + + // Ed25519/Ed448/ML-DSA require one-shot signing with EVP_DigestSign + // Also use one-shot path if no digest was specified (md == nullptr) + if (is_one_shot || md == nullptr) { // Create a new context for one-shot signing EVP_MD_CTX* sign_ctx = EVP_MD_CTX_new(); if (!sign_ctx) { throw std::runtime_error("Failed to create signing context"); } - // Initialize for one-shot signing (pass nullptr for md - Ed25519/Ed448 have built-in hash) - if (EVP_DigestSignInit(sign_ctx, nullptr, nullptr, nullptr, pkey) <= 0) { + // Get algorithm name and create PKEY_CTX for ML-DSA + const char* alg_name = getAlgorithmName(pkey); + EVP_PKEY_CTX* pkey_ctx = nullptr; + if (alg_name != nullptr) { + pkey_ctx = EVP_PKEY_CTX_new_from_name(nullptr, alg_name, nullptr); + if (!pkey_ctx) { + EVP_MD_CTX_free(sign_ctx); + throw std::runtime_error(std::string("Failed to create signing context for ") + alg_name); + } + } + + // Initialize for one-shot signing (pass nullptr for md - these algorithms have built-in hash) + if (EVP_DigestSignInit(sign_ctx, pkey_ctx ? &pkey_ctx : nullptr, nullptr, nullptr, pkey) <= 0) { EVP_MD_CTX_free(sign_ctx); - throw std::runtime_error("Failed to initialize Ed signing"); + if (pkey_ctx) + EVP_PKEY_CTX_free(pkey_ctx); + throw std::runtime_error("Failed to initialize one-shot signing"); } // Get the accumulated data from the digest context @@ -130,7 +202,10 @@ std::shared_ptr HybridSignHandle::sign(const std::shared_ptr #include +#if OPENSSL_VERSION_NUMBER >= 0x30500000L +#define RNQC_HAS_ML_DSA 1 +#else +#define RNQC_HAS_ML_DSA 0 +#endif + namespace margelo::nitro::crypto { using margelo::nitro::NativeArrayBuffer; @@ -22,17 +28,28 @@ HybridVerifyHandle::~HybridVerifyHandle() { void HybridVerifyHandle::init(const std::string& algorithm) { algorithm_name = algorithm; - md = getDigestByName(algorithm); - md_ctx = EVP_MD_CTX_new(); - if (!md_ctx) { - throw std::runtime_error("Failed to create message digest context"); - } + // For ML-DSA and other pure signature schemes, algorithm may be empty/null + if (!algorithm.empty()) { + md = getDigestByName(algorithm); - if (EVP_DigestInit_ex(md_ctx, md, nullptr) <= 0) { - EVP_MD_CTX_free(md_ctx); - md_ctx = nullptr; - throw std::runtime_error("Failed to initialize message digest"); + md_ctx = EVP_MD_CTX_new(); + if (!md_ctx) { + throw std::runtime_error("Failed to create message digest context"); + } + + if (EVP_DigestInit_ex(md_ctx, md, nullptr) <= 0) { + EVP_MD_CTX_free(md_ctx); + md_ctx = nullptr; + throw std::runtime_error("Failed to initialize message digest"); + } + } else { + // No digest for pure signature schemes like ML-DSA + md = nullptr; + md_ctx = EVP_MD_CTX_new(); + if (!md_ctx) { + throw std::runtime_error("Failed to create message digest context"); + } } } @@ -43,22 +60,60 @@ void HybridVerifyHandle::update(const std::shared_ptr& data) { auto native_data = ToNativeArrayBuffer(data); - // Accumulate raw data for potential one-shot verification (Ed25519/Ed448) + // Accumulate raw data for potential one-shot verification (Ed25519/Ed448/ML-DSA) const uint8_t* ptr = reinterpret_cast(native_data->data()); data_buffer.insert(data_buffer.end(), ptr, ptr + native_data->size()); - if (EVP_DigestUpdate(md_ctx, native_data->data(), native_data->size()) <= 0) { - unsigned long err = ERR_get_error(); - char err_buf[256]; - ERR_error_string_n(err, err_buf, sizeof(err_buf)); - throw std::runtime_error("Failed to update digest: " + std::string(err_buf)); + // Only update digest if we have one (not needed for pure signature schemes) + if (md != nullptr) { + if (EVP_DigestUpdate(md_ctx, native_data->data(), native_data->size()) <= 0) { + unsigned long err = ERR_get_error(); + char err_buf[256]; + ERR_error_string_n(err, err_buf, sizeof(err_buf)); + throw std::runtime_error("Failed to update digest: " + std::string(err_buf)); + } } } -// Check if key type requires one-shot verification (Ed25519, Ed448) +// Check if key type requires one-shot verification (Ed25519, Ed448, ML-DSA) static bool isOneShotVariant(EVP_PKEY* pkey) { int type = EVP_PKEY_id(pkey); +#if RNQC_HAS_ML_DSA + return type == EVP_PKEY_ED25519 || type == EVP_PKEY_ED448 || type == EVP_PKEY_ML_DSA_44 || type == EVP_PKEY_ML_DSA_65 || + type == EVP_PKEY_ML_DSA_87; +#else return type == EVP_PKEY_ED25519 || type == EVP_PKEY_ED448; +#endif +} + +// Get the algorithm name for creating PKEY_CTX (for ML-DSA variants) +static const char* getAlgorithmName(EVP_PKEY* pkey) { + int type = EVP_PKEY_id(pkey); +#if RNQC_HAS_ML_DSA + switch (type) { + case EVP_PKEY_ML_DSA_44: + return "ML-DSA-44"; + case EVP_PKEY_ML_DSA_65: + return "ML-DSA-65"; + case EVP_PKEY_ML_DSA_87: + return "ML-DSA-87"; + case EVP_PKEY_ED25519: + return "ED25519"; + case EVP_PKEY_ED448: + return "ED448"; + default: + return nullptr; + } +#else + switch (type) { + case EVP_PKEY_ED25519: + return "ED25519"; + case EVP_PKEY_ED448: + return "ED448"; + default: + return nullptr; + } +#endif } bool HybridVerifyHandle::verify(const std::shared_ptr& keyHandle, const std::shared_ptr& signature, @@ -78,17 +133,31 @@ bool HybridVerifyHandle::verify(const std::shared_ptr const unsigned char* sig_data = native_sig->data(); size_t sig_len = native_sig->size(); - // Ed25519/Ed448 require one-shot verification with EVP_DigestVerify - if (isOneShotVariant(pkey)) { + // Ed25519/Ed448/ML-DSA require one-shot verification with EVP_DigestVerify + // Also use one-shot path if no digest was specified (md == nullptr) + if (isOneShotVariant(pkey) || md == nullptr) { EVP_MD_CTX* verify_ctx = EVP_MD_CTX_new(); if (!verify_ctx) { throw std::runtime_error("Failed to create verification context"); } - // Initialize for one-shot verification (pass nullptr for md - Ed25519/Ed448 have built-in hash) - if (EVP_DigestVerifyInit(verify_ctx, nullptr, nullptr, nullptr, pkey) <= 0) { + // Get algorithm name and create PKEY_CTX for ML-DSA + const char* alg_name = getAlgorithmName(pkey); + EVP_PKEY_CTX* pkey_ctx = nullptr; + if (alg_name != nullptr) { + pkey_ctx = EVP_PKEY_CTX_new_from_name(nullptr, alg_name, nullptr); + if (!pkey_ctx) { + EVP_MD_CTX_free(verify_ctx); + throw std::runtime_error(std::string("Failed to create verification context for ") + alg_name); + } + } + + // Initialize for one-shot verification (pass nullptr for md - these algorithms have built-in hash) + if (EVP_DigestVerifyInit(verify_ctx, pkey_ctx ? &pkey_ctx : nullptr, nullptr, nullptr, pkey) <= 0) { EVP_MD_CTX_free(verify_ctx); - throw std::runtime_error("Failed to initialize Ed verification"); + if (pkey_ctx) + EVP_PKEY_CTX_free(pkey_ctx); + throw std::runtime_error("Failed to initialize one-shot verification"); } int result = EVP_DigestVerify(verify_ctx, sig_data, sig_len, data_buffer.data(), data_buffer.size()); diff --git a/packages/react-native-quick-crypto/deps/ncrypto b/packages/react-native-quick-crypto/deps/ncrypto new file mode 160000 index 00000000..a68a4195 --- /dev/null +++ b/packages/react-native-quick-crypto/deps/ncrypto @@ -0,0 +1 @@ +Subproject commit a68a41956174fb648916c32275a85a9a8cbe7819 diff --git a/packages/react-native-quick-crypto/deps/ncrypto/ncrypto.cc b/packages/react-native-quick-crypto/deps/ncrypto/ncrypto.cc deleted file mode 100644 index d7a26edd..00000000 --- a/packages/react-native-quick-crypto/deps/ncrypto/ncrypto.cc +++ /dev/null @@ -1,4679 +0,0 @@ -#include "ncrypto.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if OPENSSL_VERSION_MAJOR >= 3 -#include -#include -#include -#if OPENSSL_VERSION_NUMBER >= 0x30200000L -#include -#endif -#endif -#if OPENSSL_WITH_PQC -struct PQCMapping { - const char* name; - int nid; -}; - -constexpr static PQCMapping pqc_mappings[] = { - {"ML-DSA-44", EVP_PKEY_ML_DSA_44}, - {"ML-DSA-65", EVP_PKEY_ML_DSA_65}, - {"ML-DSA-87", EVP_PKEY_ML_DSA_87}, - {"ML-KEM-512", EVP_PKEY_ML_KEM_512}, - {"ML-KEM-768", EVP_PKEY_ML_KEM_768}, - {"ML-KEM-1024", EVP_PKEY_ML_KEM_1024}, - {"SLH-DSA-SHA2-128f", EVP_PKEY_SLH_DSA_SHA2_128F}, - {"SLH-DSA-SHA2-128s", EVP_PKEY_SLH_DSA_SHA2_128S}, - {"SLH-DSA-SHA2-192f", EVP_PKEY_SLH_DSA_SHA2_192F}, - {"SLH-DSA-SHA2-192s", EVP_PKEY_SLH_DSA_SHA2_192S}, - {"SLH-DSA-SHA2-256f", EVP_PKEY_SLH_DSA_SHA2_256F}, - {"SLH-DSA-SHA2-256s", EVP_PKEY_SLH_DSA_SHA2_256S}, - {"SLH-DSA-SHAKE-128f", EVP_PKEY_SLH_DSA_SHAKE_128F}, - {"SLH-DSA-SHAKE-128s", EVP_PKEY_SLH_DSA_SHAKE_128S}, - {"SLH-DSA-SHAKE-192f", EVP_PKEY_SLH_DSA_SHAKE_192F}, - {"SLH-DSA-SHAKE-192s", EVP_PKEY_SLH_DSA_SHAKE_192S}, - {"SLH-DSA-SHAKE-256f", EVP_PKEY_SLH_DSA_SHAKE_256F}, - {"SLH-DSA-SHAKE-256s", EVP_PKEY_SLH_DSA_SHAKE_256S}, -}; - -#endif - -// EVP_PKEY_CTX_set_dsa_paramgen_q_bits was added in OpenSSL 1.1.1e. -#if OPENSSL_VERSION_NUMBER < 0x1010105fL -#define EVP_PKEY_CTX_set_dsa_paramgen_q_bits(ctx, qbits) \ - EVP_PKEY_CTX_ctrl((ctx), \ - EVP_PKEY_DSA, \ - EVP_PKEY_OP_PARAMGEN, \ - EVP_PKEY_CTRL_DSA_PARAMGEN_Q_BITS, \ - (qbits), \ - nullptr) -#endif - -namespace ncrypto { -namespace { -using BignumCtxPointer = DeleteFnPtr; -using BignumGenCallbackPointer = DeleteFnPtr; -using NetscapeSPKIPointer = DeleteFnPtr; - -static constexpr int kX509NameFlagsRFC2253WithinUtf8JSON = - XN_FLAG_RFC2253 & ~ASN1_STRFLGS_ESC_MSB & ~ASN1_STRFLGS_ESC_CTRL; -} // namespace - -// ============================================================================ - -ClearErrorOnReturn::ClearErrorOnReturn(CryptoErrorList* errors) - : errors_(errors) { - ERR_clear_error(); -} - -ClearErrorOnReturn::~ClearErrorOnReturn() { - if (errors_ != nullptr) errors_->capture(); - ERR_clear_error(); -} - -int ClearErrorOnReturn::peekError() { - return ERR_peek_error(); -} - -MarkPopErrorOnReturn::MarkPopErrorOnReturn(CryptoErrorList* errors) - : errors_(errors) { - ERR_set_mark(); -} - -MarkPopErrorOnReturn::~MarkPopErrorOnReturn() { - if (errors_ != nullptr) errors_->capture(); - ERR_pop_to_mark(); -} - -int MarkPopErrorOnReturn::peekError() { - return ERR_peek_error(); -} - -CryptoErrorList::CryptoErrorList(CryptoErrorList::Option option) { - if (option == Option::CAPTURE_ON_CONSTRUCT) capture(); -} - -void CryptoErrorList::capture() { - errors_.clear(); - while (const auto err = ERR_get_error()) { - char buf[256]; - ERR_error_string_n(err, buf, sizeof(buf)); - errors_.emplace_front(buf); - } -} - -void CryptoErrorList::add(std::string error) { - errors_.push_back(error); -} - -std::optional CryptoErrorList::pop_back() { - if (errors_.empty()) return std::nullopt; - std::string error = errors_.back(); - errors_.pop_back(); - return error; -} - -std::optional CryptoErrorList::pop_front() { - if (errors_.empty()) return std::nullopt; - std::string error = errors_.front(); - errors_.pop_front(); - return error; -} - -// ============================================================================ -DataPointer DataPointer::Alloc(size_t len) { -#ifdef OPENSSL_IS_BORINGSSL - // Boringssl does not implement OPENSSL_zalloc - auto ptr = OPENSSL_malloc(len); - if (ptr == nullptr) return {}; - memset(ptr, 0, len); - return DataPointer(ptr, len); -#else - return DataPointer(OPENSSL_zalloc(len), len); -#endif -} - -DataPointer DataPointer::SecureAlloc(size_t len) { -#ifndef OPENSSL_IS_BORINGSSL - auto ptr = OPENSSL_secure_zalloc(len); - if (ptr == nullptr) return {}; - return DataPointer(ptr, len, true); -#else - // BoringSSL does not implement the OPENSSL_secure_zalloc API. - auto ptr = OPENSSL_malloc(len); - if (ptr == nullptr) return {}; - memset(ptr, 0, len); - return DataPointer(ptr, len); -#endif -} - -size_t DataPointer::GetSecureHeapUsed() { -#ifndef OPENSSL_IS_BORINGSSL - return CRYPTO_secure_malloc_initialized() ? CRYPTO_secure_used() : 0; -#else - // BoringSSL does not have the secure heap and therefore - // will always return 0. - return 0; -#endif -} - -DataPointer::InitSecureHeapResult DataPointer::TryInitSecureHeap(size_t amount, - size_t min) { -#ifndef OPENSSL_IS_BORINGSSL - switch (CRYPTO_secure_malloc_init(amount, min)) { - case 0: - return InitSecureHeapResult::FAILED; - case 2: - return InitSecureHeapResult::UNABLE_TO_MEMORY_MAP; - case 1: - return InitSecureHeapResult::OK; - default: - return InitSecureHeapResult::FAILED; - } -#else - // BoringSSL does not actually support the secure heap - return InitSecureHeapResult::FAILED; -#endif -} - -DataPointer DataPointer::Copy(const Buffer& buffer) { - return DataPointer(OPENSSL_memdup(buffer.data, buffer.len), buffer.len); -} - -DataPointer::DataPointer(void* data, size_t length, bool secure) - : data_(data), len_(length), secure_(secure) {} - -DataPointer::DataPointer(const Buffer& buffer, bool secure) - : data_(buffer.data), len_(buffer.len), secure_(secure) {} - -DataPointer::DataPointer(DataPointer&& other) noexcept - : data_(other.data_), len_(other.len_), secure_(other.secure_) { - other.data_ = nullptr; - other.len_ = 0; - other.secure_ = false; -} - -DataPointer& DataPointer::operator=(DataPointer&& other) noexcept { - if (this == &other) return *this; - this->~DataPointer(); - return *new (this) DataPointer(std::move(other)); -} - -DataPointer::~DataPointer() { - reset(); -} - -void DataPointer::zero() { - if (!data_) return; - OPENSSL_cleanse(data_, len_); -} - -void DataPointer::reset(void* data, size_t length) { - if (data_ != nullptr) { - if (secure_) { - OPENSSL_secure_clear_free(data_, len_); - } else { - OPENSSL_clear_free(data_, len_); - } - } - data_ = data; - len_ = length; -} - -void DataPointer::reset(const Buffer& buffer) { - reset(buffer.data, buffer.len); -} - -Buffer DataPointer::release() { - Buffer buf{ - .data = data_, - .len = len_, - }; - data_ = nullptr; - len_ = 0; - return buf; -} - -DataPointer DataPointer::resize(size_t len) { - size_t actual_len = std::min(len_, len); - auto buf = release(); - if (actual_len == len_) return DataPointer(buf.data, actual_len); - buf.data = OPENSSL_realloc(buf.data, actual_len); - buf.len = actual_len; - return DataPointer(buf); -} - -// ============================================================================ -bool isFipsEnabled() { - ClearErrorOnReturn clear_error_on_return; -#if OPENSSL_VERSION_MAJOR >= 3 - return EVP_default_properties_is_fips_enabled(nullptr) == 1; -#else - return FIPS_mode() == 1; -#endif -} - -bool setFipsEnabled(bool enable, CryptoErrorList* errors) { - if (isFipsEnabled() == enable) return true; - ClearErrorOnReturn clearErrorOnReturn(errors); -#if OPENSSL_VERSION_MAJOR >= 3 - return EVP_default_properties_enable_fips(nullptr, enable ? 1 : 0) == 1 && - EVP_default_properties_is_fips_enabled(nullptr); -#else - return FIPS_mode_set(enable ? 1 : 0) == 1; -#endif -} - -bool testFipsEnabled() { - ClearErrorOnReturn clear_error_on_return; -#if OPENSSL_VERSION_MAJOR >= 3 - OSSL_PROVIDER* fips_provider = nullptr; - if (OSSL_PROVIDER_available(nullptr, "fips")) { - fips_provider = OSSL_PROVIDER_load(nullptr, "fips"); - } - if (fips_provider == nullptr) return false; - int result = OSSL_PROVIDER_self_test(fips_provider); - OSSL_PROVIDER_unload(fips_provider); - return result; -#else -#ifdef OPENSSL_FIPS - return FIPS_selftest(); -#else // OPENSSL_FIPS - return false; -#endif // OPENSSL_FIPS -#endif -} - -// ============================================================================ -// Bignum -BignumPointer::BignumPointer(BIGNUM* bignum) : bn_(bignum) {} - -BignumPointer::BignumPointer(const unsigned char* data, size_t len) - : BignumPointer(BN_bin2bn(data, len, nullptr)) {} - -BignumPointer::BignumPointer(BignumPointer&& other) noexcept - : bn_(other.release()) {} - -BignumPointer BignumPointer::New() { - return BignumPointer(BN_new()); -} - -BignumPointer BignumPointer::NewSecure() { -#ifdef OPENSSL_IS_BORINGSSL - // Boringssl does not implement BN_secure_new. - return New(); -#else - return BignumPointer(BN_secure_new()); -#endif -} - -BignumPointer& BignumPointer::operator=(BignumPointer&& other) noexcept { - if (this == &other) return *this; - this->~BignumPointer(); - return *new (this) BignumPointer(std::move(other)); -} - -BignumPointer::~BignumPointer() { - reset(); -} - -void BignumPointer::reset(BIGNUM* bn) { - bn_.reset(bn); -} - -void BignumPointer::reset(const unsigned char* data, size_t len) { - reset(BN_bin2bn(data, len, nullptr)); -} - -BIGNUM* BignumPointer::release() { - return bn_.release(); -} - -size_t BignumPointer::byteLength() const { - if (bn_ == nullptr) return 0; - return BN_num_bytes(bn_.get()); -} - -DataPointer BignumPointer::encode() const { - return EncodePadded(bn_.get(), byteLength()); -} - -DataPointer BignumPointer::encodePadded(size_t size) const { - return EncodePadded(bn_.get(), size); -} - -size_t BignumPointer::encodeInto(unsigned char* out) const { - if (!bn_) return 0; - return BN_bn2bin(bn_.get(), out); -} - -size_t BignumPointer::encodePaddedInto(unsigned char* out, size_t size) const { - if (!bn_) return 0; - return BN_bn2binpad(bn_.get(), out, size); -} - -DataPointer BignumPointer::Encode(const BIGNUM* bn) { - return EncodePadded(bn, bn != nullptr ? BN_num_bytes(bn) : 0); -} - -bool BignumPointer::setWord(unsigned long w) { // NOLINT(runtime/int) - if (!bn_) return false; - return BN_set_word(bn_.get(), w) == 1; -} - -unsigned long BignumPointer::GetWord(const BIGNUM* bn) { // NOLINT(runtime/int) - return BN_get_word(bn); -} - -unsigned long BignumPointer::getWord() const { // NOLINT(runtime/int) - if (!bn_) return 0; - return GetWord(bn_.get()); -} - -DataPointer BignumPointer::EncodePadded(const BIGNUM* bn, size_t s) { - if (bn == nullptr) return DataPointer(); - size_t size = std::max(s, static_cast(GetByteCount(bn))); - auto buf = DataPointer::Alloc(size); - BN_bn2binpad(bn, reinterpret_cast(buf.get()), size); - return buf; -} -size_t BignumPointer::EncodePaddedInto(const BIGNUM* bn, - unsigned char* out, - size_t size) { - if (bn == nullptr) return 0; - return BN_bn2binpad(bn, out, size); -} - -int BignumPointer::operator<=>(const BignumPointer& other) const noexcept { - if (bn_ == nullptr && other.bn_ != nullptr) return -1; - if (bn_ != nullptr && other.bn_ == nullptr) return 1; - if (bn_ == nullptr && other.bn_ == nullptr) return 0; - return BN_cmp(bn_.get(), other.bn_.get()); -} - -int BignumPointer::operator<=>(const BIGNUM* other) const noexcept { - if (bn_ == nullptr && other != nullptr) return -1; - if (bn_ != nullptr && other == nullptr) return 1; - if (bn_ == nullptr && other == nullptr) return 0; - return BN_cmp(bn_.get(), other); -} - -DataPointer BignumPointer::toHex() const { - if (!bn_) return {}; - char* hex = BN_bn2hex(bn_.get()); - if (!hex) return {}; - return DataPointer(hex, strlen(hex)); -} - -int BignumPointer::GetBitCount(const BIGNUM* bn) { - return BN_num_bits(bn); -} - -int BignumPointer::GetByteCount(const BIGNUM* bn) { - return BN_num_bytes(bn); -} - -bool BignumPointer::isZero() const { - return bn_ && BN_is_zero(bn_.get()); -} - -bool BignumPointer::isOne() const { - return bn_ && BN_is_one(bn_.get()); -} - -const BIGNUM* BignumPointer::One() { - return BN_value_one(); -} - -BignumPointer BignumPointer::clone() { - if (!bn_) return {}; - return BignumPointer(BN_dup(bn_.get())); -} - -int BignumPointer::isPrime(int nchecks, - BignumPointer::PrimeCheckCallback innerCb) const { - BignumCtxPointer ctx(BN_CTX_new()); - BignumGenCallbackPointer cb(nullptr); - if (innerCb != nullptr) { - cb = BignumGenCallbackPointer(BN_GENCB_new()); - if (!cb) [[unlikely]] - return -1; - BN_GENCB_set( - cb.get(), - // TODO(@jasnell): This could be refactored to allow inlining. - // Not too important right now tho. - [](int a, int b, BN_GENCB* ctx) mutable -> int { - PrimeCheckCallback& ptr = - *static_cast(BN_GENCB_get_arg(ctx)); - return ptr(a, b) ? 1 : 0; - }, - &innerCb); - } - return BN_is_prime_ex(get(), nchecks, ctx.get(), cb.get()); -} - -BignumPointer BignumPointer::NewPrime(const PrimeConfig& params, - PrimeCheckCallback cb) { - BignumPointer prime(BN_new()); - if (!prime || !prime.generate(params, std::move(cb))) { - return {}; - } - return prime; -} - -bool BignumPointer::generate(const PrimeConfig& params, - PrimeCheckCallback innerCb) const { - // BN_generate_prime_ex() calls RAND_bytes_ex() internally. - // Make sure the CSPRNG is properly seeded. - std::ignore = CSPRNG(nullptr, 0); - BignumGenCallbackPointer cb(nullptr); - if (innerCb != nullptr) { - cb = BignumGenCallbackPointer(BN_GENCB_new()); - if (!cb) [[unlikely]] - return -1; - BN_GENCB_set( - cb.get(), - [](int a, int b, BN_GENCB* ctx) mutable -> int { - PrimeCheckCallback& ptr = - *static_cast(BN_GENCB_get_arg(ctx)); - return ptr(a, b) ? 1 : 0; - }, - &innerCb); - } - if (BN_generate_prime_ex(get(), - params.bits, - params.safe ? 1 : 0, - params.add.get(), - params.rem.get(), - cb.get()) == 0) { - return false; - } - - return true; -} - -BignumPointer BignumPointer::NewSub(const BignumPointer& a, - const BignumPointer& b) { - BignumPointer res = New(); - if (!res) return {}; - if (!BN_sub(res.get(), a.get(), b.get())) { - return {}; - } - return res; -} - -BignumPointer BignumPointer::NewLShift(size_t length) { - BignumPointer res = New(); - if (!res) return {}; - if (!BN_lshift(res.get(), One(), length)) { - return {}; - } - return res; -} - -// ============================================================================ -// Utility methods - -bool CSPRNG(void* buffer, size_t length) { - auto buf = reinterpret_cast(buffer); - do { - if (1 == RAND_status()) { -#if OPENSSL_VERSION_MAJOR >= 3 - if (1 == RAND_bytes_ex(nullptr, buf, length, 0)) { - return true; - } -#else - while (length > INT_MAX && 1 == RAND_bytes(buf, INT_MAX)) { - buf += INT_MAX; - length -= INT_MAX; - } - if (length <= INT_MAX && 1 == RAND_bytes(buf, static_cast(length))) - return true; -#endif - } -#if OPENSSL_VERSION_MAJOR >= 3 - const auto code = ERR_peek_last_error(); - // A misconfigured OpenSSL 3 installation may report 1 from RAND_poll() - // and RAND_status() but fail in RAND_bytes() if it cannot look up - // a matching algorithm for the CSPRNG. - if (ERR_GET_LIB(code) == ERR_LIB_RAND) { - const auto reason = ERR_GET_REASON(code); - if (reason == RAND_R_ERROR_INSTANTIATING_DRBG || - reason == RAND_R_UNABLE_TO_FETCH_DRBG || - reason == RAND_R_UNABLE_TO_CREATE_DRBG) { - return false; - } - } -#endif - } while (1 == RAND_poll()); - - return false; -} - -int NoPasswordCallback(char* buf, int size, int rwflag, void* u) { - return 0; -} - -int PasswordCallback(char* buf, int size, int rwflag, void* u) { - auto passphrase = static_cast*>(u); - if (passphrase != nullptr) { - size_t buflen = static_cast(size); - size_t len = passphrase->len; - if (buflen < len) return -1; - memcpy(buf, reinterpret_cast(passphrase->data), len); - return len; - } - - return -1; -} - -// Algorithm: http://howardhinnant.github.io/date_algorithms.html -constexpr int days_from_epoch(int y, unsigned m, unsigned d) { - y -= m <= 2; - const int era = (y >= 0 ? y : y - 399) / 400; - const unsigned yoe = static_cast(y - era * 400); // [0, 399] - const unsigned doy = - (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; // [0, 365] - const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096] - return era * 146097 + static_cast(doe) - 719468; -} - -#ifndef OPENSSL_IS_BORINGSSL -// tm must be in UTC -// using time_t causes problems on 32-bit systems and windows x64. -int64_t PortableTimeGM(struct tm* t) { - int year = t->tm_year + 1900; - int month = t->tm_mon; - if (month > 11) { - year += month / 12; - month %= 12; - } else if (month < 0) { - int years_diff = (11 - month) / 12; - year -= years_diff; - month += 12 * years_diff; - } - int days_since_epoch = days_from_epoch(year, month + 1, t->tm_mday); - - return 60 * (60 * (24LL * static_cast(days_since_epoch) + - t->tm_hour) + - t->tm_min) + - t->tm_sec; -} -#endif - -// ============================================================================ -// SPKAC - -bool VerifySpkac(const char* input, size_t length) { -#ifdef OPENSSL_IS_BORINGSSL - // OpenSSL uses EVP_DecodeBlock, which explicitly removes trailing characters, - // while BoringSSL uses EVP_DecodedLength and EVP_DecodeBase64, which do not. - // As such, we trim those characters here for compatibility. - // - // find_last_not_of can return npos, which is the maximum value of size_t. - // The + 1 will force a roll-ver to 0, which is the correct value. in that - // case. - length = std::string_view(input, length).find_last_not_of(" \n\r\t") + 1; -#endif - NetscapeSPKIPointer spki(NETSCAPE_SPKI_b64_decode(input, length)); - if (!spki) return false; - - EVPKeyPointer pkey(X509_PUBKEY_get(spki->spkac->pubkey)); - return pkey ? NETSCAPE_SPKI_verify(spki.get(), pkey.get()) > 0 : false; -} - -BIOPointer ExportPublicKey(const char* input, size_t length) { - BIOPointer bio(BIO_new(BIO_s_mem())); - if (!bio) return {}; - -#ifdef OPENSSL_IS_BORINGSSL - // OpenSSL uses EVP_DecodeBlock, which explicitly removes trailing characters, - // while BoringSSL uses EVP_DecodedLength and EVP_DecodeBase64, which do not. - // As such, we trim those characters here for compatibility. - length = std::string_view(input, length).find_last_not_of(" \n\r\t") + 1; -#endif - NetscapeSPKIPointer spki(NETSCAPE_SPKI_b64_decode(input, length)); - if (!spki) return {}; - - EVPKeyPointer pkey(NETSCAPE_SPKI_get_pubkey(spki.get())); - if (!pkey) return {}; - - if (PEM_write_bio_PUBKEY(bio.get(), pkey.get()) <= 0) return {}; - - return bio; -} - -Buffer ExportChallenge(const char* input, size_t length) { -#ifdef OPENSSL_IS_BORINGSSL - // OpenSSL uses EVP_DecodeBlock, which explicitly removes trailing characters, - // while BoringSSL uses EVP_DecodedLength and EVP_DecodeBase64, which do not. - // As such, we trim those characters here for compatibility. - length = std::string_view(input, length).find_last_not_of(" \n\r\t") + 1; -#endif - NetscapeSPKIPointer sp(NETSCAPE_SPKI_b64_decode(input, length)); - if (!sp) return {}; - - unsigned char* buf = nullptr; - int buf_size = ASN1_STRING_to_UTF8(&buf, sp->spkac->challenge); - if (buf_size >= 0) { - return { - .data = reinterpret_cast(buf), - .len = static_cast(buf_size), - }; - } - - return {}; -} - -// ============================================================================ -namespace { -enum class AltNameOption { - NONE, - UTF8, -}; - -bool IsSafeAltName(const char* name, size_t length, AltNameOption option) { - for (size_t i = 0; i < length; i++) { - char c = name[i]; - switch (c) { - case '"': - case '\\': - // These mess with encoding rules. - // Fall through. - case ',': - // Commas make it impossible to split the list of subject alternative - // names unambiguously, which is why we have to escape. - // Fall through. - case '\'': - // Single quotes are unlikely to appear in any legitimate values, but - // they could be used to make a value look like it was escaped (i.e., - // enclosed in single/double quotes). - return false; - default: - if (option == AltNameOption::UTF8) { - // In UTF8 strings, we require escaping for any ASCII control - // character, but NOT for non-ASCII characters. Note that all bytes of - // any code point that consists of more than a single byte have their - // MSB set. - if (static_cast(c) < ' ' || c == '\x7f') { - return false; - } - } else { - // Check if the char is a control character or non-ASCII character. - // Note that char may or may not be a signed type. Regardless, - // non-ASCII values will always be outside of this range. - if (c < ' ' || c > '~') { - return false; - } - } - } - } - return true; -} - -void PrintAltName(const BIOPointer& out, - const char* name, - size_t length, - AltNameOption option = AltNameOption::NONE, - const char* safe_prefix = nullptr) { - if (IsSafeAltName(name, length, option)) { - // For backward-compatibility, append "safe" names without any - // modifications. - if (safe_prefix != nullptr) { - BIO_printf(out.get(), "%s:", safe_prefix); - } - BIO_write(out.get(), name, length); - } else { - // If a name is not "safe", we cannot embed it without special - // encoding. This does not usually happen, but we don't want to hide - // it from the user either. We use JSON compatible escaping here. - BIO_write(out.get(), "\"", 1); - if (safe_prefix != nullptr) { - BIO_printf(out.get(), "%s:", safe_prefix); - } - for (size_t j = 0; j < length; j++) { - char c = static_cast(name[j]); - if (c == '\\') { - BIO_write(out.get(), "\\\\", 2); - } else if (c == '"') { - BIO_write(out.get(), "\\\"", 2); - } else if ((c >= ' ' && c != ',' && c <= '~') || - (option == AltNameOption::UTF8 && (c & 0x80))) { - // Note that the above condition explicitly excludes commas, which means - // that those are encoded as Unicode escape sequences in the "else" - // block. That is not strictly necessary, and Node.js itself would parse - // it correctly either way. We only do this to account for third-party - // code that might be splitting the string at commas (as Node.js itself - // used to do). - BIO_write(out.get(), &c, 1); - } else { - // Control character or non-ASCII character. We treat everything as - // Latin-1, which corresponds to the first 255 Unicode code points. - const char hex[] = "0123456789abcdef"; - char u[] = {'\\', 'u', '0', '0', hex[(c & 0xf0) >> 4], hex[c & 0x0f]}; - BIO_write(out.get(), u, sizeof(u)); - } - } - BIO_write(out.get(), "\"", 1); - } -} - -// This function emulates the behavior of i2v_GENERAL_NAME in a safer and less -// ambiguous way. "othername:" entries use the GENERAL_NAME_print format. -bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { - if (gen->type == GEN_DNS) { - ASN1_IA5STRING* name = gen->d.dNSName; - BIO_write(out.get(), "DNS:", 4); - // Note that the preferred name syntax (see RFCs 5280 and 1034) with - // wildcards is a subset of what we consider "safe", so spec-compliant DNS - // names will never need to be escaped. - PrintAltName(out, reinterpret_cast(name->data), name->length); - } else if (gen->type == GEN_EMAIL) { - ASN1_IA5STRING* name = gen->d.rfc822Name; - BIO_write(out.get(), "email:", 6); - PrintAltName(out, reinterpret_cast(name->data), name->length); - } else if (gen->type == GEN_URI) { - ASN1_IA5STRING* name = gen->d.uniformResourceIdentifier; - BIO_write(out.get(), "URI:", 4); - // The set of "safe" names was designed to include just about any URI, - // with a few exceptions, most notably URIs that contains commas (see - // RFC 2396). In other words, most legitimate URIs will not require - // escaping. - PrintAltName(out, reinterpret_cast(name->data), name->length); - } else if (gen->type == GEN_DIRNAME) { - // Earlier versions of Node.js used X509_NAME_oneline to print the X509_NAME - // object. The format was non standard and should be avoided. The use of - // X509_NAME_oneline is discouraged by OpenSSL but was required for backward - // compatibility. Conveniently, X509_NAME_oneline produced ASCII and the - // output was unlikely to contains commas or other characters that would - // require escaping. However, it SHOULD NOT produce ASCII output since an - // RFC5280 AttributeValue may be a UTF8String. - // Newer versions of Node.js have since switched to X509_NAME_print_ex to - // produce a better format at the cost of backward compatibility. The new - // format may contain Unicode characters and it is likely to contain commas, - // which require escaping. Fortunately, the recently safeguarded function - // PrintAltName handles all of that safely. - BIO_printf(out.get(), "DirName:"); - BIOPointer tmp(BIO_new(BIO_s_mem())); - NCRYPTO_ASSERT_TRUE(tmp); - if (X509_NAME_print_ex( - tmp.get(), gen->d.dirn, 0, kX509NameFlagsRFC2253WithinUtf8JSON) < - 0) { - return false; - } - char* oline = nullptr; - long n_bytes = BIO_get_mem_data(tmp.get(), &oline); // NOLINT(runtime/int) - NCRYPTO_ASSERT_TRUE(n_bytes >= 0); - PrintAltName(out, - oline, - static_cast(n_bytes), - ncrypto::AltNameOption::UTF8, - nullptr); - } else if (gen->type == GEN_IPADD) { - BIO_printf(out.get(), "IP Address:"); - const ASN1_OCTET_STRING* ip = gen->d.ip; - const unsigned char* b = ip->data; - if (ip->length == 4) { - BIO_printf(out.get(), "%d.%d.%d.%d", b[0], b[1], b[2], b[3]); - } else if (ip->length == 16) { - for (unsigned int j = 0; j < 8; j++) { - uint16_t pair = (b[2 * j] << 8) | b[2 * j + 1]; - BIO_printf(out.get(), (j == 0) ? "%X" : ":%X", pair); - } - } else { -#if OPENSSL_VERSION_MAJOR >= 3 - BIO_printf(out.get(), "", ip->length); -#else - BIO_printf(out.get(), ""); -#endif - } - } else if (gen->type == GEN_RID) { - // Unlike OpenSSL's default implementation, never print the OID as text and - // instead always print its numeric representation. - char oline[256]; - OBJ_obj2txt(oline, sizeof(oline), gen->d.rid, true); - BIO_printf(out.get(), "Registered ID:%s", oline); - } else if (gen->type == GEN_OTHERNAME) { - // The format that is used here is based on OpenSSL's implementation of - // GENERAL_NAME_print (as of OpenSSL 3.0.1). Earlier versions of Node.js - // instead produced the same format as i2v_GENERAL_NAME, which was somewhat - // awkward, especially when passed to translatePeerCertificate. - bool unicode = true; - const char* prefix = nullptr; - // OpenSSL 1.1.1 does not support othername in GENERAL_NAME_print and may - // not define these NIDs. -#if OPENSSL_VERSION_MAJOR >= 3 - int nid = OBJ_obj2nid(gen->d.otherName->type_id); - switch (nid) { - case NID_id_on_SmtpUTF8Mailbox: - prefix = "SmtpUTF8Mailbox"; - break; - case NID_XmppAddr: - prefix = "XmppAddr"; - break; - case NID_SRVName: - prefix = "SRVName"; - unicode = false; - break; - case NID_ms_upn: - prefix = "UPN"; - break; - case NID_NAIRealm: - prefix = "NAIRealm"; - break; - } -#endif // OPENSSL_VERSION_MAJOR >= 3 - int val_type = gen->d.otherName->value->type; - if (prefix == nullptr || (unicode && val_type != V_ASN1_UTF8STRING) || - (!unicode && val_type != V_ASN1_IA5STRING)) { - BIO_printf(out.get(), "othername:"); - } else { - BIO_printf(out.get(), "othername:"); - if (unicode) { - auto name = gen->d.otherName->value->value.utf8string; - PrintAltName(out, - reinterpret_cast(name->data), - name->length, - AltNameOption::UTF8, - prefix); - } else { - auto name = gen->d.otherName->value->value.ia5string; - PrintAltName(out, - reinterpret_cast(name->data), - name->length, - AltNameOption::NONE, - prefix); - } - } - } else if (gen->type == GEN_X400) { - // TODO(tniessen): this is what OpenSSL does, implement properly instead - BIO_printf(out.get(), "X400Name:"); - } else if (gen->type == GEN_EDIPARTY) { - // TODO(tniessen): this is what OpenSSL does, implement properly instead - BIO_printf(out.get(), "EdiPartyName:"); - } else { - // This is safe because X509V3_EXT_d2i would have returned nullptr in this - // case already. - unreachable(); - } - - return true; -} -} // namespace - -bool SafeX509SubjectAltNamePrint(const BIOPointer& out, X509_EXTENSION* ext) { - auto ret = OBJ_obj2nid(X509_EXTENSION_get_object(ext)); - if (ret != NID_subject_alt_name) return false; - - GENERAL_NAMES* names = static_cast(X509V3_EXT_d2i(ext)); - if (names == nullptr) return false; - - bool ok = true; - - for (OPENSSL_SIZE_T i = 0; i < sk_GENERAL_NAME_num(names); i++) { - GENERAL_NAME* gen = sk_GENERAL_NAME_value(names, i); - - if (i != 0) BIO_write(out.get(), ", ", 2); - - if (!(ok = ncrypto::PrintGeneralName(out, gen))) { - break; - } - } - sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); - - return ok; -} - -bool SafeX509InfoAccessPrint(const BIOPointer& out, X509_EXTENSION* ext) { - auto ret = OBJ_obj2nid(X509_EXTENSION_get_object(ext)); - if (ret != NID_info_access) return false; - - AUTHORITY_INFO_ACCESS* descs = - static_cast(X509V3_EXT_d2i(ext)); - if (descs == nullptr) return false; - - bool ok = true; - - for (OPENSSL_SIZE_T i = 0; i < sk_ACCESS_DESCRIPTION_num(descs); i++) { - ACCESS_DESCRIPTION* desc = sk_ACCESS_DESCRIPTION_value(descs, i); - - if (i != 0) BIO_write(out.get(), "\n", 1); - - char objtmp[80]; - i2t_ASN1_OBJECT(objtmp, sizeof(objtmp), desc->method); - BIO_printf(out.get(), "%s - ", objtmp); - if (!(ok = ncrypto::PrintGeneralName(out, desc->location))) { - break; - } - } - sk_ACCESS_DESCRIPTION_pop_free(descs, ACCESS_DESCRIPTION_free); - -#if OPENSSL_VERSION_MAJOR < 3 - BIO_write(out.get(), "\n", 1); -#endif - - return ok; -} - -// ============================================================================ -// X509Pointer - -X509Pointer::X509Pointer(X509* x509) : cert_(x509) {} - -X509Pointer::X509Pointer(X509Pointer&& other) noexcept - : cert_(other.release()) {} - -X509Pointer& X509Pointer::operator=(X509Pointer&& other) noexcept { - if (this == &other) return *this; - this->~X509Pointer(); - return *new (this) X509Pointer(std::move(other)); -} - -X509Pointer::~X509Pointer() { - reset(); -} - -void X509Pointer::reset(X509* x509) { - cert_.reset(x509); -} - -X509* X509Pointer::release() { - return cert_.release(); -} - -X509View X509Pointer::view() const { - return X509View(cert_.get()); -} - -BIOPointer X509View::toPEM() const { - ClearErrorOnReturn clearErrorOnReturn; - if (cert_ == nullptr) return {}; - BIOPointer bio(BIO_new(BIO_s_mem())); - if (!bio) return {}; - if (PEM_write_bio_X509(bio.get(), const_cast(cert_)) <= 0) return {}; - return bio; -} - -BIOPointer X509View::toDER() const { - ClearErrorOnReturn clearErrorOnReturn; - if (cert_ == nullptr) return {}; - BIOPointer bio(BIO_new(BIO_s_mem())); - if (!bio) return {}; - if (i2d_X509_bio(bio.get(), const_cast(cert_)) <= 0) return {}; - return bio; -} - -const X509Name X509View::getSubjectName() const { - ClearErrorOnReturn clearErrorOnReturn; - if (cert_ == nullptr) return {}; - return X509Name(X509_get_subject_name(cert_)); -} - -const X509Name X509View::getIssuerName() const { - ClearErrorOnReturn clearErrorOnReturn; - if (cert_ == nullptr) return {}; - return X509Name(X509_get_issuer_name(cert_)); -} - -BIOPointer X509View::getSubject() const { - ClearErrorOnReturn clearErrorOnReturn; - if (cert_ == nullptr) return {}; - BIOPointer bio(BIO_new(BIO_s_mem())); - if (!bio) return {}; - if (X509_NAME_print_ex(bio.get(), - X509_get_subject_name(cert_), - 0, - kX509NameFlagsMultiline) <= 0) { - return {}; - } - return bio; -} - -BIOPointer X509View::getSubjectAltName() const { - ClearErrorOnReturn clearErrorOnReturn; - if (cert_ == nullptr) return {}; - BIOPointer bio(BIO_new(BIO_s_mem())); - if (!bio) return {}; - int index = X509_get_ext_by_NID(cert_, NID_subject_alt_name, -1); - if (index < 0 || - !SafeX509SubjectAltNamePrint(bio, X509_get_ext(cert_, index))) { - return {}; - } - return bio; -} - -BIOPointer X509View::getIssuer() const { - ClearErrorOnReturn clearErrorOnReturn; - if (cert_ == nullptr) return {}; - BIOPointer bio(BIO_new(BIO_s_mem())); - if (!bio) return {}; - if (X509_NAME_print_ex( - bio.get(), X509_get_issuer_name(cert_), 0, kX509NameFlagsMultiline) <= - 0) { - return {}; - } - return bio; -} - -BIOPointer X509View::getInfoAccess() const { - ClearErrorOnReturn clearErrorOnReturn; - if (cert_ == nullptr) return {}; - BIOPointer bio(BIO_new(BIO_s_mem())); - if (!bio) return {}; - int index = X509_get_ext_by_NID(cert_, NID_info_access, -1); - if (index < 0) return {}; - if (!SafeX509InfoAccessPrint(bio, X509_get_ext(cert_, index))) { - return {}; - } - return bio; -} - -BIOPointer X509View::getValidFrom() const { - ClearErrorOnReturn clearErrorOnReturn; - if (cert_ == nullptr) return {}; - BIOPointer bio(BIO_new(BIO_s_mem())); - if (!bio) return {}; - ASN1_TIME_print(bio.get(), X509_get_notBefore(cert_)); - return bio; -} - -BIOPointer X509View::getValidTo() const { - ClearErrorOnReturn clearErrorOnReturn; - if (cert_ == nullptr) return {}; - BIOPointer bio(BIO_new(BIO_s_mem())); - if (!bio) return {}; - ASN1_TIME_print(bio.get(), X509_get_notAfter(cert_)); - return bio; -} - -int64_t X509View::getValidToTime() const { -#ifdef OPENSSL_IS_BORINGSSL - // Boringssl does not implement ASN1_TIME_to_tm in a public way, - // and only recently added ASN1_TIME_to_posix. Some boringssl - // users on older version may still need to patch around this - // or use a different implementation. - int64_t tp; - ASN1_TIME_to_posix(X509_get0_notAfter(cert_), &tp); - return tp; -#else - struct tm tp; - ASN1_TIME_to_tm(X509_get0_notAfter(cert_), &tp); - return PortableTimeGM(&tp); -#endif -} - -int64_t X509View::getValidFromTime() const { -#ifdef OPENSSL_IS_BORINGSSL - int64_t tp; - ASN1_TIME_to_posix(X509_get0_notBefore(cert_), &tp); - return tp; -#else - struct tm tp; - ASN1_TIME_to_tm(X509_get0_notBefore(cert_), &tp); - return PortableTimeGM(&tp); -#endif -} - -DataPointer X509View::getSerialNumber() const { - ClearErrorOnReturn clearErrorOnReturn; - if (cert_ == nullptr) return {}; - if (ASN1_INTEGER* serial_number = - X509_get_serialNumber(const_cast(cert_))) { - if (auto bn = BignumPointer(ASN1_INTEGER_to_BN(serial_number, nullptr))) { - return bn.toHex(); - } - } - return {}; -} - -Result X509View::getPublicKey() const { - ClearErrorOnReturn clearErrorOnReturn; - if (cert_ == nullptr) return Result(EVPKeyPointer{}); - auto pkey = EVPKeyPointer(X509_get_pubkey(const_cast(cert_))); - if (!pkey) return Result(ERR_get_error()); - return pkey; -} - -StackOfASN1 X509View::getKeyUsage() const { - ClearErrorOnReturn clearErrorOnReturn; - if (cert_ == nullptr) return {}; - return StackOfASN1(static_cast( - X509_get_ext_d2i(cert_, NID_ext_key_usage, nullptr, nullptr))); -} - -bool X509View::isCA() const { - ClearErrorOnReturn clearErrorOnReturn; - if (cert_ == nullptr) return false; - return X509_check_ca(const_cast(cert_)) == 1; -} - -bool X509View::isIssuedBy(const X509View& issuer) const { - ClearErrorOnReturn clearErrorOnReturn; - if (cert_ == nullptr || issuer.cert_ == nullptr) return false; - return X509_check_issued(const_cast(issuer.cert_), - const_cast(cert_)) == X509_V_OK; -} - -bool X509View::checkPrivateKey(const EVPKeyPointer& pkey) const { - ClearErrorOnReturn clearErrorOnReturn; - if (cert_ == nullptr || pkey == nullptr) return false; - return X509_check_private_key(const_cast(cert_), pkey.get()) == 1; -} - -bool X509View::checkPublicKey(const EVPKeyPointer& pkey) const { - ClearErrorOnReturn clearErrorOnReturn; - if (cert_ == nullptr || pkey == nullptr) return false; - return X509_verify(const_cast(cert_), pkey.get()) == 1; -} - -X509View::CheckMatch X509View::checkHost(const std::string_view host, - int flags, - DataPointer* peerName) const { - ClearErrorOnReturn clearErrorOnReturn; - if (cert_ == nullptr) return CheckMatch::NO_MATCH; - char* peername; - switch (X509_check_host( - const_cast(cert_), host.data(), host.size(), flags, &peername)) { - case 0: - return CheckMatch::NO_MATCH; - case 1: { - if (peername != nullptr) { - DataPointer name(peername, strlen(peername)); - if (peerName != nullptr) *peerName = std::move(name); - } - return CheckMatch::MATCH; - } - case -2: - return CheckMatch::INVALID_NAME; - default: - return CheckMatch::OPERATION_FAILED; - } -} - -X509View::CheckMatch X509View::checkEmail(const std::string_view email, - int flags) const { - ClearErrorOnReturn clearErrorOnReturn; - if (cert_ == nullptr) return CheckMatch::NO_MATCH; - switch (X509_check_email( - const_cast(cert_), email.data(), email.size(), flags)) { - case 0: - return CheckMatch::NO_MATCH; - case 1: - return CheckMatch::MATCH; - case -2: - return CheckMatch::INVALID_NAME; - default: - return CheckMatch::OPERATION_FAILED; - } -} - -X509View::CheckMatch X509View::checkIp(const std::string_view ip, - int flags) const { - ClearErrorOnReturn clearErrorOnReturn; - if (cert_ == nullptr) return CheckMatch::NO_MATCH; - switch (X509_check_ip_asc(const_cast(cert_), ip.data(), flags)) { - case 0: - return CheckMatch::NO_MATCH; - case 1: - return CheckMatch::MATCH; - case -2: - return CheckMatch::INVALID_NAME; - default: - return CheckMatch::OPERATION_FAILED; - } -} - -X509View X509View::From(const SSLPointer& ssl) { - ClearErrorOnReturn clear_error_on_return; - if (!ssl) return {}; - return X509View(SSL_get_certificate(ssl.get())); -} - -X509View X509View::From(const SSLCtxPointer& ctx) { - ClearErrorOnReturn clear_error_on_return; - if (!ctx) return {}; - return X509View(SSL_CTX_get0_certificate(ctx.get())); -} - -std::optional X509View::getFingerprint( - const Digest& method) const { - unsigned int md_size; - unsigned char md[EVP_MAX_MD_SIZE]; - static constexpr char hex[] = "0123456789ABCDEF"; - - if (X509_digest(get(), method, md, &md_size)) { - if (md_size == 0) return std::nullopt; - std::string fingerprint((md_size * 3) - 1, 0); - for (unsigned int i = 0; i < md_size; i++) { - auto idx = 3 * i; - fingerprint[idx] = hex[(md[i] & 0xf0) >> 4]; - fingerprint[idx + 1] = hex[(md[i] & 0x0f)]; - if (i == md_size - 1) break; - fingerprint[idx + 2] = ':'; - } - - return fingerprint; - } - - return std::nullopt; -} - -X509Pointer X509View::clone() const { - ClearErrorOnReturn clear_error_on_return; - if (!cert_) return {}; - return X509Pointer(X509_dup(const_cast(cert_))); -} - -Result X509Pointer::Parse( - Buffer buffer) { - ClearErrorOnReturn clearErrorOnReturn; - BIOPointer bio(BIO_new_mem_buf(buffer.data, buffer.len)); - if (!bio) return Result(ERR_get_error()); - - X509Pointer pem( - PEM_read_bio_X509_AUX(bio.get(), nullptr, NoPasswordCallback, nullptr)); - if (pem) return Result(std::move(pem)); - BIO_reset(bio.get()); - - X509Pointer der(d2i_X509_bio(bio.get(), nullptr)); - if (der) return Result(std::move(der)); - - return Result(ERR_get_error()); -} - -bool X509View::enumUsages(UsageCallback callback) const { - if (cert_ == nullptr) return false; - StackOfASN1 eku(static_cast( - X509_get_ext_d2i(cert_, NID_ext_key_usage, nullptr, nullptr))); - if (!eku) return false; - const int count = sk_ASN1_OBJECT_num(eku.get()); - char buf[256]{}; - - for (int i = 0; i < count; i++) { - if (OBJ_obj2txt(buf, sizeof(buf), sk_ASN1_OBJECT_value(eku.get(), i), 1) >= - 0) { - callback(buf); - } - } - return true; -} - -bool X509View::ifRsa(KeyCallback callback) const { - if (cert_ == nullptr) return true; - OSSL3_CONST EVP_PKEY* pkey = X509_get0_pubkey(cert_); - auto id = EVP_PKEY_id(pkey); - if (id == EVP_PKEY_RSA || id == EVP_PKEY_RSA2 || id == EVP_PKEY_RSA_PSS) { - Rsa rsa(EVP_PKEY_get0_RSA(pkey)); - if (!rsa) [[unlikely]] - return true; - return callback(rsa); - } - return true; -} - -bool X509View::ifEc(KeyCallback callback) const { - if (cert_ == nullptr) return true; - OSSL3_CONST EVP_PKEY* pkey = X509_get0_pubkey(cert_); - auto id = EVP_PKEY_id(pkey); - if (id == EVP_PKEY_EC) { - Ec ec(EVP_PKEY_get0_EC_KEY(pkey)); - if (!ec) [[unlikely]] - return true; - return callback(ec); - } - return true; -} - -X509Pointer X509Pointer::IssuerFrom(const SSLPointer& ssl, - const X509View& view) { - return IssuerFrom(SSL_get_SSL_CTX(ssl.get()), view); -} - -X509Pointer X509Pointer::IssuerFrom(const SSL_CTX* ctx, const X509View& cert) { - X509_STORE* store = SSL_CTX_get_cert_store(ctx); - DeleteFnPtr store_ctx( - X509_STORE_CTX_new()); - X509Pointer result; - X509* issuer; - if (store_ctx.get() != nullptr && - X509_STORE_CTX_init(store_ctx.get(), store, nullptr, nullptr) == 1 && - X509_STORE_CTX_get1_issuer(&issuer, store_ctx.get(), cert.get()) == 1) { - result.reset(issuer); - } - return result; -} - -X509Pointer X509Pointer::PeerFrom(const SSLPointer& ssl) { - return X509Pointer(SSL_get_peer_certificate(ssl.get())); -} - -// When adding or removing errors below, please also update the list in the API -// documentation. See the "OpenSSL Error Codes" section of doc/api/errors.md -// Also *please* update the respective section in doc/api/tls.md as well -const char* X509Pointer::ErrorCode(int32_t err) { // NOLINT(runtime/int) -#define CASE(CODE) \ - case X509_V_ERR_##CODE: \ - return #CODE; - switch (err) { - CASE(UNABLE_TO_GET_ISSUER_CERT) - CASE(UNABLE_TO_GET_CRL) - CASE(UNABLE_TO_DECRYPT_CERT_SIGNATURE) - CASE(UNABLE_TO_DECRYPT_CRL_SIGNATURE) - CASE(UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY) - CASE(CERT_SIGNATURE_FAILURE) - CASE(CRL_SIGNATURE_FAILURE) - CASE(CERT_NOT_YET_VALID) - CASE(CERT_HAS_EXPIRED) - CASE(CRL_NOT_YET_VALID) - CASE(CRL_HAS_EXPIRED) - CASE(ERROR_IN_CERT_NOT_BEFORE_FIELD) - CASE(ERROR_IN_CERT_NOT_AFTER_FIELD) - CASE(ERROR_IN_CRL_LAST_UPDATE_FIELD) - CASE(ERROR_IN_CRL_NEXT_UPDATE_FIELD) - CASE(OUT_OF_MEM) - CASE(DEPTH_ZERO_SELF_SIGNED_CERT) - CASE(SELF_SIGNED_CERT_IN_CHAIN) - CASE(UNABLE_TO_GET_ISSUER_CERT_LOCALLY) - CASE(UNABLE_TO_VERIFY_LEAF_SIGNATURE) - CASE(CERT_CHAIN_TOO_LONG) - CASE(CERT_REVOKED) - CASE(INVALID_CA) - CASE(PATH_LENGTH_EXCEEDED) - CASE(INVALID_PURPOSE) - CASE(CERT_UNTRUSTED) - CASE(CERT_REJECTED) - CASE(HOSTNAME_MISMATCH) - } -#undef CASE - return "UNSPECIFIED"; -} - -std::optional X509Pointer::ErrorReason(int32_t err) { - if (err == X509_V_OK) return std::nullopt; - return X509_verify_cert_error_string(err); -} - -// ============================================================================ -// BIOPointer - -BIOPointer::BIOPointer(BIO* bio) : bio_(bio) {} - -BIOPointer::BIOPointer(BIOPointer&& other) noexcept : bio_(other.release()) {} - -BIOPointer& BIOPointer::operator=(BIOPointer&& other) noexcept { - if (this == &other) return *this; - this->~BIOPointer(); - return *new (this) BIOPointer(std::move(other)); -} - -BIOPointer::~BIOPointer() { - reset(); -} - -void BIOPointer::reset(BIO* bio) { - bio_.reset(bio); -} - -BIO* BIOPointer::release() { - return bio_.release(); -} - -bool BIOPointer::resetBio() const { - if (!bio_) return 0; - return BIO_reset(bio_.get()) == 1; -} - -BIOPointer BIOPointer::NewMem() { - return BIOPointer(BIO_new(BIO_s_mem())); -} - -BIOPointer BIOPointer::NewSecMem() { -#ifdef OPENSSL_IS_BORINGSSL - // Boringssl does not implement the BIO_s_secmem API. - return BIOPointer(BIO_new(BIO_s_mem())); -#else - return BIOPointer(BIO_new(BIO_s_secmem())); -#endif -} - -BIOPointer BIOPointer::New(const BIO_METHOD* method) { - return BIOPointer(BIO_new(method)); -} - -BIOPointer BIOPointer::New(const void* data, size_t len) { - return BIOPointer(BIO_new_mem_buf(data, len)); -} - -BIOPointer BIOPointer::NewFile(const char* filename, const char* mode) { - return BIOPointer(BIO_new_file(filename, mode)); -} - -BIOPointer BIOPointer::NewFp(FILE* fd, int close_flag) { - return BIOPointer(BIO_new_fp(fd, close_flag)); -} - -BIOPointer BIOPointer::New(const BIGNUM* bn) { - auto res = NewMem(); - if (!res || !BN_print(res.get(), bn)) return {}; - return res; -} - -int BIOPointer::Write(BIOPointer* bio, std::string_view message) { - if (bio == nullptr || !*bio) return 0; - return BIO_write(bio->get(), message.data(), message.size()); -} - -// ============================================================================ -// DHPointer - -namespace { -bool EqualNoCase(const std::string_view a, const std::string_view b) { - if (a.size() != b.size()) return false; - return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) { - return std::tolower(a) == std::tolower(b); - }); -} -} // namespace - -DHPointer::DHPointer(DH* dh) : dh_(dh) {} - -DHPointer::DHPointer(DHPointer&& other) noexcept : dh_(other.release()) {} - -DHPointer& DHPointer::operator=(DHPointer&& other) noexcept { - if (this == &other) return *this; - this->~DHPointer(); - return *new (this) DHPointer(std::move(other)); -} - -DHPointer::~DHPointer() { - reset(); -} - -void DHPointer::reset(DH* dh) { - dh_.reset(dh); -} - -DH* DHPointer::release() { - return dh_.release(); -} - -BignumPointer DHPointer::FindGroup(const std::string_view name, - FindGroupOption option) { -#define V(n, p) \ - if (EqualNoCase(name, n)) return BignumPointer(p(nullptr)); - if (option != FindGroupOption::NO_SMALL_PRIMES) { -#ifndef OPENSSL_IS_BORINGSSL - // Boringssl does not support the 768 and 1024 small primes - V("modp1", BN_get_rfc2409_prime_768); - V("modp2", BN_get_rfc2409_prime_1024); -#endif - V("modp5", BN_get_rfc3526_prime_1536); - } - V("modp14", BN_get_rfc3526_prime_2048); - V("modp15", BN_get_rfc3526_prime_3072); - V("modp16", BN_get_rfc3526_prime_4096); - V("modp17", BN_get_rfc3526_prime_6144); - V("modp18", BN_get_rfc3526_prime_8192); -#undef V - return {}; -} - -BignumPointer DHPointer::GetStandardGenerator() { - auto bn = BignumPointer::New(); - if (!bn) return {}; - if (!bn.setWord(DH_GENERATOR_2)) return {}; - return bn; -} - -DHPointer DHPointer::FromGroup(const std::string_view name, - FindGroupOption option) { - auto group = FindGroup(name, option); - if (!group) return {}; // Unable to find the named group. - - auto generator = GetStandardGenerator(); - if (!generator) return {}; // Unable to create the generator. - - return New(std::move(group), std::move(generator)); -} - -DHPointer DHPointer::New(BignumPointer&& p, BignumPointer&& g) { - if (!p || !g) return {}; - - DHPointer dh(DH_new()); - if (!dh) return {}; - - if (DH_set0_pqg(dh.get(), p.get(), nullptr, g.get()) != 1) return {}; - - // If the call above is successful, the DH object takes ownership of the - // BIGNUMs, so we must release them here. Unfortunately coverity does not - // know that so we need to tell it not to complain. - // coverity[resource_leak] - p.release(); - // coverity[resource_leak] - g.release(); - - return dh; -} - -DHPointer DHPointer::New(size_t bits, unsigned int generator) { - DHPointer dh(DH_new()); - if (!dh) return {}; - - if (DH_generate_parameters_ex(dh.get(), bits, generator, nullptr) != 1) { - return {}; - } - - return dh; -} - -DHPointer::CheckResult DHPointer::check() { - ClearErrorOnReturn clearErrorOnReturn; - if (!dh_) return DHPointer::CheckResult::NONE; - int codes = 0; - if (DH_check(dh_.get(), &codes) != 1) - return DHPointer::CheckResult::CHECK_FAILED; - return static_cast(codes); -} - -DHPointer::CheckPublicKeyResult DHPointer::checkPublicKey( - const BignumPointer& pub_key) { - ClearErrorOnReturn clearErrorOnReturn; - if (!pub_key || !dh_) { - return DHPointer::CheckPublicKeyResult::CHECK_FAILED; - } - int codes = 0; - if (DH_check_pub_key(dh_.get(), pub_key.get(), &codes) != 1) { - return DHPointer::CheckPublicKeyResult::CHECK_FAILED; - } -#ifndef OPENSSL_IS_BORINGSSL - // Boringssl does not define DH_CHECK_PUBKEY_TOO_SMALL or TOO_LARGE - if (codes & DH_CHECK_PUBKEY_TOO_SMALL) { - return DHPointer::CheckPublicKeyResult::TOO_SMALL; - } else if (codes & DH_CHECK_PUBKEY_TOO_LARGE) { - return DHPointer::CheckPublicKeyResult::TOO_LARGE; - } -#endif - if (codes != 0) { - return DHPointer::CheckPublicKeyResult::INVALID; - } - return CheckPublicKeyResult::NONE; -} - -DataPointer DHPointer::getPrime() const { - if (!dh_) return {}; - const BIGNUM* p; - DH_get0_pqg(dh_.get(), &p, nullptr, nullptr); - return BignumPointer::Encode(p); -} - -DataPointer DHPointer::getGenerator() const { - if (!dh_) return {}; - const BIGNUM* g; - DH_get0_pqg(dh_.get(), nullptr, nullptr, &g); - return BignumPointer::Encode(g); -} - -DataPointer DHPointer::getPublicKey() const { - if (!dh_) return {}; - const BIGNUM* pub_key; - DH_get0_key(dh_.get(), &pub_key, nullptr); - return BignumPointer::Encode(pub_key); -} - -DataPointer DHPointer::getPrivateKey() const { - if (!dh_) return {}; - const BIGNUM* pvt_key; - DH_get0_key(dh_.get(), nullptr, &pvt_key); - return BignumPointer::Encode(pvt_key); -} - -DataPointer DHPointer::generateKeys() const { - ClearErrorOnReturn clearErrorOnReturn; - if (!dh_) return {}; - - // Key generation failed - if (!DH_generate_key(dh_.get())) return {}; - - return getPublicKey(); -} - -size_t DHPointer::size() const { - if (!dh_) return 0; - int ret = DH_size(dh_.get()); - // DH_size can return a -1 on error but we just want to return a 0 - // in that case so we don't wrap around when returning the size_t. - return ret >= 0 ? static_cast(ret) : 0; -} - -DataPointer DHPointer::computeSecret(const BignumPointer& peer) const { - ClearErrorOnReturn clearErrorOnReturn; - if (!dh_ || !peer) return {}; - - auto dp = DataPointer::Alloc(size()); - if (!dp) return {}; - - int size = - DH_compute_key(static_cast(dp.get()), peer.get(), dh_.get()); - if (size < 0) return {}; - - // The size of the computed key can be smaller than the size of the DH key. - // We want to make sure that the key is correctly padded. - if (static_cast(size) < dp.size()) { - const size_t padding = dp.size() - size; - uint8_t* data = static_cast(dp.get()); - memmove(data + padding, data, size); - memset(data, 0, padding); - } - - return dp; -} - -bool DHPointer::setPublicKey(BignumPointer&& key) { - if (!dh_) return false; - if (DH_set0_key(dh_.get(), key.get(), nullptr) == 1) { - // If DH_set0_key returns successfully, then dh_ takes ownership of the - // BIGNUM, so we must release it here. Unfortunately coverity does not - // know that so we need to tell it not to complain. - // coverity[resource_leak] - key.release(); - return true; - } - return false; -} - -bool DHPointer::setPrivateKey(BignumPointer&& key) { - if (!dh_) return false; - if (DH_set0_key(dh_.get(), nullptr, key.get()) == 1) { - // If DH_set0_key returns successfully, then dh_ takes ownership of the - // BIGNUM, so we must release it here. Unfortunately coverity does not - // know that so we need to tell it not to complain. - // coverity[resource_leak] - key.release(); - return true; - } - return false; -} - -DataPointer DHPointer::stateless(const EVPKeyPointer& ourKey, - const EVPKeyPointer& theirKey) { - size_t out_size; - if (!ourKey || !theirKey) return {}; - - auto ctx = EVPKeyCtxPointer::New(ourKey); - if (!ctx || EVP_PKEY_derive_init(ctx.get()) <= 0 || - EVP_PKEY_derive_set_peer(ctx.get(), theirKey.get()) <= 0 || - EVP_PKEY_derive(ctx.get(), nullptr, &out_size) <= 0) { - return {}; - } - - if (out_size == 0) return {}; - - auto out = DataPointer::Alloc(out_size); - if (EVP_PKEY_derive( - ctx.get(), reinterpret_cast(out.get()), &out_size) <= 0) { - return {}; - } - - if (out_size < out.size()) { - const size_t padding = out.size() - out_size; - uint8_t* data = static_cast(out.get()); - memmove(data + padding, data, out_size); - memset(data, 0, padding); - } - - return out; -} - -// ============================================================================ -// KDF - -const EVP_MD* getDigestByName(const char* name) { - // Historically, "dss1" and "DSS1" were DSA aliases for SHA-1 - // exposed through the public API. - if (strcmp(name, "dss1") == 0 || strcmp(name, "DSS1") == 0) [[unlikely]] { - return EVP_sha1(); - } - return EVP_get_digestbyname(name); -} - -const EVP_CIPHER* getCipherByName(const char* name) { - return EVP_get_cipherbyname(name); -} - -bool checkHkdfLength(const Digest& md, size_t length) { - // HKDF-Expand computes up to 255 HMAC blocks, each having as many bits as - // the output of the hash function. 255 is a hard limit because HKDF appends - // an 8-bit counter to each HMAC'd message, starting at 1. - static constexpr size_t kMaxDigestMultiplier = 255; - size_t max_length = md.size() * kMaxDigestMultiplier; - if (length > max_length) return false; - return true; -} - -DataPointer hkdf(const Digest& md, - const Buffer& key, - const Buffer& info, - const Buffer& salt, - size_t length) { - ClearErrorOnReturn clearErrorOnReturn; - - if (!checkHkdfLength(md, length) || info.len > INT_MAX || - salt.len > INT_MAX) { - return {}; - } - - auto ctx = EVPKeyCtxPointer::NewFromID(EVP_PKEY_HKDF); - // OpenSSL < 3.0.0 accepted only a void* as the argument of - // EVP_PKEY_CTX_set_hkdf_md. - const EVP_MD* md_ptr = md; - if (!ctx || !EVP_PKEY_derive_init(ctx.get()) || - !EVP_PKEY_CTX_set_hkdf_md(ctx.get(), md_ptr) || - !EVP_PKEY_CTX_add1_hkdf_info(ctx.get(), info.data, info.len)) { - return {}; - } - - std::string_view actual_salt; - static const char default_salt[EVP_MAX_MD_SIZE] = {0}; - if (salt.len > 0) { - actual_salt = {reinterpret_cast(salt.data), salt.len}; - } else { - actual_salt = {default_salt, static_cast(md.size())}; - } - - // We do not use EVP_PKEY_HKDF_MODE_EXTRACT_AND_EXPAND because and instead - // implement the extraction step ourselves because EVP_PKEY_derive does not - // handle zero-length keys, which are required for Web Crypto. - // TODO(jasnell): Once OpenSSL 1.1.1 support is dropped completely, and once - // BoringSSL is confirmed to support it, wen can hopefully drop this and use - // EVP_KDF directly which does support zero length keys. - unsigned char pseudorandom_key[EVP_MAX_MD_SIZE]; - unsigned pseudorandom_key_len = sizeof(pseudorandom_key); - - if (HMAC(md, - actual_salt.data(), - actual_salt.size(), - key.data, - key.len, - pseudorandom_key, - &pseudorandom_key_len) == nullptr) { - return {}; - } - if (!EVP_PKEY_CTX_hkdf_mode(ctx.get(), EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) || - !EVP_PKEY_CTX_set1_hkdf_key( - ctx.get(), pseudorandom_key, pseudorandom_key_len)) { - return {}; - } - - auto buf = DataPointer::Alloc(length); - if (!buf) return {}; - - if (EVP_PKEY_derive( - ctx.get(), static_cast(buf.get()), &length) <= 0) { - return {}; - } - - return buf; -} - -bool checkScryptParams(uint64_t N, uint64_t r, uint64_t p, uint64_t maxmem) { - return EVP_PBE_scrypt(nullptr, 0, nullptr, 0, N, r, p, maxmem, nullptr, 0) == - 1; -} - -DataPointer scrypt(const Buffer& pass, - const Buffer& salt, - uint64_t N, - uint64_t r, - uint64_t p, - uint64_t maxmem, - size_t length) { - ClearErrorOnReturn clearErrorOnReturn; - - if (pass.len > INT_MAX || salt.len > INT_MAX) { - return {}; - } - - auto dp = DataPointer::Alloc(length); - if (dp && EVP_PBE_scrypt(pass.data, - pass.len, - salt.data, - salt.len, - N, - r, - p, - maxmem, - reinterpret_cast(dp.get()), - length)) { - return dp; - } - - return {}; -} - -DataPointer pbkdf2(const Digest& md, - const Buffer& pass, - const Buffer& salt, - uint32_t iterations, - size_t length) { - ClearErrorOnReturn clearErrorOnReturn; - - if (pass.len > INT_MAX || salt.len > INT_MAX || length > INT_MAX) { - return {}; - } - - auto dp = DataPointer::Alloc(length); - const EVP_MD* md_ptr = md; - if (dp && PKCS5_PBKDF2_HMAC(pass.data, - pass.len, - salt.data, - salt.len, - iterations, - md_ptr, - length, - reinterpret_cast(dp.get()))) { - return dp; - } - - return {}; -} - -#if OPENSSL_VERSION_NUMBER >= 0x30200000L -#ifndef OPENSSL_NO_ARGON2 -DataPointer argon2(const Buffer& pass, - const Buffer& salt, - uint32_t lanes, - size_t length, - uint32_t memcost, - uint32_t iter, - uint32_t version, - const Buffer& secret, - const Buffer& ad, - Argon2Type type) { - ClearErrorOnReturn clearErrorOnReturn; - - std::string_view algorithm; - switch (type) { - case Argon2Type::ARGON2I: - algorithm = "ARGON2I"; - break; - case Argon2Type::ARGON2D: - algorithm = "ARGON2D"; - break; - case Argon2Type::ARGON2ID: - algorithm = "ARGON2ID"; - break; - default: - // Invalid Argon2 type - return {}; - } - - // creates a new library context to avoid locking when running concurrently - auto ctx = DeleteFnPtr{OSSL_LIB_CTX_new()}; - if (!ctx) { - return {}; - } - - // required if threads > 1 - if (lanes > 1 && OSSL_set_max_threads(ctx.get(), lanes) != 1) { - return {}; - } - - auto kdf = DeleteFnPtr{ - EVP_KDF_fetch(ctx.get(), algorithm.data(), nullptr)}; - if (!kdf) { - return {}; - } - - auto kctx = - DeleteFnPtr{EVP_KDF_CTX_new(kdf.get())}; - if (!kctx) { - return {}; - } - - std::vector params; - params.reserve(9); - - params.push_back(OSSL_PARAM_construct_octet_string( - OSSL_KDF_PARAM_PASSWORD, - const_cast(pass.len > 0 ? pass.data : ""), - pass.len)); - params.push_back(OSSL_PARAM_construct_octet_string( - OSSL_KDF_PARAM_SALT, const_cast(salt.data), salt.len)); - params.push_back(OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_THREADS, &lanes)); - params.push_back( - OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_LANES, &lanes)); - params.push_back( - OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, &memcost)); - params.push_back(OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ITER, &iter)); - - if (ad.len != 0) { - params.push_back(OSSL_PARAM_construct_octet_string( - OSSL_KDF_PARAM_ARGON2_AD, const_cast(ad.data), ad.len)); - } - - if (secret.len != 0) { - params.push_back(OSSL_PARAM_construct_octet_string( - OSSL_KDF_PARAM_SECRET, - const_cast(secret.data), - secret.len)); - } - - params.push_back(OSSL_PARAM_construct_end()); - - auto dp = DataPointer::Alloc(length); - if (dp && EVP_KDF_derive(kctx.get(), - reinterpret_cast(dp.get()), - length, - params.data()) == 1) { - return dp; - } - - return {}; -} -#endif -#endif - -// ============================================================================ - -EVPKeyPointer::PrivateKeyEncodingConfig::PrivateKeyEncodingConfig( - const PrivateKeyEncodingConfig& other) - : PrivateKeyEncodingConfig( - other.output_key_object, other.format, other.type) { - cipher = other.cipher; - if (other.passphrase.has_value()) { - auto& otherPassphrase = other.passphrase.value(); - auto newPassphrase = DataPointer::Alloc(otherPassphrase.size()); - memcpy(newPassphrase.get(), otherPassphrase.get(), otherPassphrase.size()); - passphrase = std::move(newPassphrase); - } -} - -EVPKeyPointer::AsymmetricKeyEncodingConfig::AsymmetricKeyEncodingConfig( - bool output_key_object, PKFormatType format, PKEncodingType type) - : output_key_object(output_key_object), format(format), type(type) {} - -EVPKeyPointer::PrivateKeyEncodingConfig& -EVPKeyPointer::PrivateKeyEncodingConfig::operator=( - const PrivateKeyEncodingConfig& other) { - if (this == &other) return *this; - this->~PrivateKeyEncodingConfig(); - return *new (this) PrivateKeyEncodingConfig(other); -} - -EVPKeyPointer EVPKeyPointer::New() { - return EVPKeyPointer(EVP_PKEY_new()); -} - -EVPKeyPointer EVPKeyPointer::NewRawPublic( - int id, const Buffer& data) { - if (id == 0) return {}; - return EVPKeyPointer( - EVP_PKEY_new_raw_public_key(id, nullptr, data.data, data.len)); -} - -EVPKeyPointer EVPKeyPointer::NewRawPrivate( - int id, const Buffer& data) { - if (id == 0) return {}; - return EVPKeyPointer( - EVP_PKEY_new_raw_private_key(id, nullptr, data.data, data.len)); -} - -#if OPENSSL_WITH_PQC -EVPKeyPointer EVPKeyPointer::NewRawSeed( - int id, const Buffer& data) { - if (id == 0) return {}; - - OSSL_PARAM params[] = { - OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_ML_DSA_SEED, - const_cast(data.data), - data.len), - OSSL_PARAM_END}; - - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(id, nullptr); - if (ctx == nullptr) return {}; - - EVP_PKEY* pkey = nullptr; - if (ctx == nullptr || EVP_PKEY_fromdata_init(ctx) <= 0 || - EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) <= 0) { - EVP_PKEY_CTX_free(ctx); - return {}; - } - - return EVPKeyPointer(pkey); -} -#endif - -EVPKeyPointer EVPKeyPointer::NewDH(DHPointer&& dh) { - if (!dh) return {}; - auto key = New(); - if (!key) return {}; - if (EVP_PKEY_assign_DH(key.get(), dh.get())) { - dh.release(); - } - return key; -} - -EVPKeyPointer EVPKeyPointer::NewRSA(RSAPointer&& rsa) { - if (!rsa) return {}; - auto key = New(); - if (!key) return {}; - if (EVP_PKEY_assign_RSA(key.get(), rsa.get())) { - rsa.release(); - } - return key; -} - -EVPKeyPointer::EVPKeyPointer(EVP_PKEY* pkey) : pkey_(pkey) {} - -EVPKeyPointer::EVPKeyPointer(EVPKeyPointer&& other) noexcept - : pkey_(other.release()) {} - -EVPKeyPointer& EVPKeyPointer::operator=(EVPKeyPointer&& other) noexcept { - if (this == &other) return *this; - this->~EVPKeyPointer(); - return *new (this) EVPKeyPointer(std::move(other)); -} - -EVPKeyPointer::~EVPKeyPointer() { - reset(); -} - -void EVPKeyPointer::reset(EVP_PKEY* pkey) { - pkey_.reset(pkey); -} - -EVP_PKEY* EVPKeyPointer::release() { - return pkey_.release(); -} - -int EVPKeyPointer::id(const EVP_PKEY* key) { - if (key == nullptr) return 0; - int type = EVP_PKEY_id(key); -#if OPENSSL_WITH_PQC - // EVP_PKEY_id returns -1 when EVP_PKEY_* is only implemented in a provider - // which is the case for all post-quantum NIST algorithms - // one suggested way would be to use a chain of `EVP_PKEY_is_a` - // https://github.com/openssl/openssl/issues/27738#issuecomment-3013215870 - // or, this way there are less calls to the OpenSSL provider, just - // getting the name once - if (type == -1) { - const char* type_name = EVP_PKEY_get0_type_name(key); - if (type_name == nullptr) return -1; - - for (const auto& mapping : pqc_mappings) { - if (strcmp(type_name, mapping.name) == 0) { - return mapping.nid; - } - } - } -#endif - return type; -} - -int EVPKeyPointer::base_id(const EVP_PKEY* key) { - if (key == nullptr) return 0; - return EVP_PKEY_base_id(key); -} - -int EVPKeyPointer::id() const { - return id(get()); -} - -int EVPKeyPointer::base_id() const { - return base_id(get()); -} - -int EVPKeyPointer::bits() const { - if (get() == nullptr) return 0; - return EVP_PKEY_bits(get()); -} - -size_t EVPKeyPointer::size() const { - if (get() == nullptr) return 0; - return EVP_PKEY_size(get()); -} - -EVPKeyCtxPointer EVPKeyPointer::newCtx() const { - if (!pkey_) return {}; - return EVPKeyCtxPointer::New(*this); -} - -size_t EVPKeyPointer::rawPublicKeySize() const { - if (!pkey_) return 0; - size_t len = 0; - if (EVP_PKEY_get_raw_public_key(get(), nullptr, &len) == 1) return len; - return 0; -} - -size_t EVPKeyPointer::rawPrivateKeySize() const { - if (!pkey_) return 0; - size_t len = 0; - if (EVP_PKEY_get_raw_private_key(get(), nullptr, &len) == 1) return len; - return 0; -} - -DataPointer EVPKeyPointer::rawPublicKey() const { - if (!pkey_) return {}; - if (auto data = DataPointer::Alloc(rawPublicKeySize())) { - const Buffer buf = data; - size_t len = data.size(); - if (EVP_PKEY_get_raw_public_key(get(), buf.data, &len) != 1) return {}; - return data; - } - return {}; -} - -#if OPENSSL_WITH_PQC -DataPointer EVPKeyPointer::rawSeed() const { - if (!pkey_) return {}; - - // Determine seed length and parameter name based on key type - size_t seed_len; - const char* param_name; - - switch (id()) { - case EVP_PKEY_ML_DSA_44: - case EVP_PKEY_ML_DSA_65: - case EVP_PKEY_ML_DSA_87: - seed_len = 32; // ML-DSA uses 32-byte seeds - param_name = OSSL_PKEY_PARAM_ML_DSA_SEED; - break; - case EVP_PKEY_ML_KEM_512: - case EVP_PKEY_ML_KEM_768: - case EVP_PKEY_ML_KEM_1024: - seed_len = 64; // ML-KEM uses 64-byte seeds - param_name = OSSL_PKEY_PARAM_ML_KEM_SEED; - break; - default: - unreachable(); - } - - if (auto data = DataPointer::Alloc(seed_len)) { - const Buffer buf = data; - size_t len = data.size(); - - if (EVP_PKEY_get_octet_string_param( - get(), param_name, buf.data, len, &seed_len) != 1) - return {}; - return data; - } - return {}; -} -#endif - -DataPointer EVPKeyPointer::rawPrivateKey() const { - if (!pkey_) return {}; - if (auto data = DataPointer::Alloc(rawPrivateKeySize())) { - const Buffer buf = data; - size_t len = data.size(); - if (EVP_PKEY_get_raw_private_key(get(), buf.data, &len) != 1) return {}; - return data; - } - return {}; -} - -BIOPointer EVPKeyPointer::derPublicKey() const { - if (!pkey_) return {}; - auto bio = BIOPointer::NewMem(); - if (!bio) return {}; - if (!i2d_PUBKEY_bio(bio.get(), get())) return {}; - return bio; -} - -bool EVPKeyPointer::assign(const ECKeyPointer& eckey) { - if (!pkey_ || !eckey) return {}; - return EVP_PKEY_assign_EC_KEY(pkey_.get(), eckey.get()); -} - -bool EVPKeyPointer::set(const ECKeyPointer& eckey) { - if (!pkey_ || !eckey) return false; - return EVP_PKEY_set1_EC_KEY(pkey_.get(), eckey); -} - -EVPKeyPointer::operator const EC_KEY*() const { - if (!pkey_) return nullptr; - return EVP_PKEY_get0_EC_KEY(pkey_.get()); -} - -namespace { -EVPKeyPointer::ParseKeyResult TryParsePublicKeyInner(const BIOPointer& bp, - const char* name, - auto&& parse) { - if (!bp.resetBio()) { - return EVPKeyPointer::ParseKeyResult(EVPKeyPointer::PKParseError::FAILED); - } - unsigned char* der_data; - long der_len; // NOLINT(runtime/int) - - // This skips surrounding data and decodes PEM to DER. - { - MarkPopErrorOnReturn mark_pop_error_on_return; - if (PEM_bytes_read_bio( - &der_data, &der_len, nullptr, name, bp.get(), nullptr, nullptr) != - 1) - return EVPKeyPointer::ParseKeyResult( - EVPKeyPointer::PKParseError::NOT_RECOGNIZED); - } - DataPointer data(der_data, der_len); - - // OpenSSL might modify the pointer, so we need to make a copy before parsing. - const unsigned char* p = der_data; - EVPKeyPointer pkey(parse(&p, der_len)); - if (!pkey) - return EVPKeyPointer::ParseKeyResult(EVPKeyPointer::PKParseError::FAILED); - return EVPKeyPointer::ParseKeyResult(std::move(pkey)); -} - -constexpr bool IsASN1Sequence(const unsigned char* data, - size_t size, - size_t* data_offset, - size_t* data_size) { - if (size < 2 || data[0] != 0x30) return false; - - if (data[1] & 0x80) { - // Long form. - size_t n_bytes = data[1] & ~0x80; - if (n_bytes + 2 > size || n_bytes > sizeof(size_t)) return false; - size_t length = 0; - for (size_t i = 0; i < n_bytes; i++) length = (length << 8) | data[i + 2]; - *data_offset = 2 + n_bytes; - *data_size = std::min(size - 2 - n_bytes, length); - } else { - // Short form. - *data_offset = 2; - *data_size = std::min(size - 2, data[1]); - } - - return true; -} - -constexpr bool IsEncryptedPrivateKeyInfo( - const Buffer& buffer) { - // Both PrivateKeyInfo and EncryptedPrivateKeyInfo start with a SEQUENCE. - if (buffer.len == 0 || buffer.data == nullptr) return false; - size_t offset, len; - if (!IsASN1Sequence(buffer.data, buffer.len, &offset, &len)) return false; - - // A PrivateKeyInfo sequence always starts with an integer whereas an - // EncryptedPrivateKeyInfo starts with an AlgorithmIdentifier. - return len >= 1 && buffer.data[offset] != 2; -} - -} // namespace - -bool EVPKeyPointer::IsRSAPrivateKey(const Buffer& buffer) { - // Both RSAPrivateKey and RSAPublicKey structures start with a SEQUENCE. - size_t offset, len; - if (!IsASN1Sequence(buffer.data, buffer.len, &offset, &len)) return false; - - // An RSAPrivateKey sequence always starts with a single-byte integer whose - // value is either 0 or 1, whereas an RSAPublicKey starts with the modulus - // (which is the product of two primes and therefore at least 4), so we can - // decide the type of the structure based on the first three bytes of the - // sequence. - return len >= 3 && buffer.data[offset] == 2 && buffer.data[offset + 1] == 1 && - !(buffer.data[offset + 2] & 0xfe); -} - -EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKeyPEM( - const Buffer& buffer) { - auto bp = BIOPointer::New(buffer.data, buffer.len); - if (!bp) return ParseKeyResult(PKParseError::FAILED); - - // Try parsing as SubjectPublicKeyInfo (SPKI) first. - if (auto ret = TryParsePublicKeyInner( - bp, - "PUBLIC KEY", - [](const unsigned char** p, long l) { // NOLINT(runtime/int) - return d2i_PUBKEY(nullptr, p, l); - })) { - return ret; - } - - // Maybe it is PKCS#1. - if (auto ret = TryParsePublicKeyInner( - bp, - "RSA PUBLIC KEY", - [](const unsigned char** p, long l) { // NOLINT(runtime/int) - return d2i_PublicKey(EVP_PKEY_RSA, nullptr, p, l); - })) { - return ret; - } - - // X.509 fallback. - if (auto ret = TryParsePublicKeyInner( - bp, - "CERTIFICATE", - [](const unsigned char** p, long l) { // NOLINT(runtime/int) - X509Pointer x509(d2i_X509(nullptr, p, l)); - return x509 ? X509_get_pubkey(x509.get()) : nullptr; - })) { - return ret; - }; - - return ParseKeyResult(PKParseError::NOT_RECOGNIZED); -} - -EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKey( - const PublicKeyEncodingConfig& config, - const Buffer& buffer) { - if (config.format == PKFormatType::PEM) { - return TryParsePublicKeyPEM(buffer); - } - - if (config.format != PKFormatType::DER) { - return ParseKeyResult(PKParseError::FAILED); - } - - const unsigned char* start = buffer.data; - - EVP_PKEY* key = nullptr; - - if (config.type == PKEncodingType::PKCS1 && - (key = d2i_PublicKey(EVP_PKEY_RSA, nullptr, &start, buffer.len))) { - return EVPKeyPointer::ParseKeyResult(EVPKeyPointer(key)); - } - - if (config.type == PKEncodingType::SPKI && - (key = d2i_PUBKEY(nullptr, &start, buffer.len))) { - return EVPKeyPointer::ParseKeyResult(EVPKeyPointer(key)); - } - - return ParseKeyResult(PKParseError::FAILED); -} - -namespace { -Buffer GetPassphrase( - const EVPKeyPointer::PrivateKeyEncodingConfig& config) { - Buffer pass{ - // OpenSSL will not actually dereference this pointer, so it can be any - // non-null pointer. We cannot assert that directly, which is why we - // intentionally use a pointer that will likely cause a segmentation fault - // when dereferenced. - .data = reinterpret_cast(-1), - .len = 0, - }; - if (config.passphrase.has_value()) { - auto& passphrase = config.passphrase.value(); - // The pass.data can't be a nullptr, even if the len is zero or else - // openssl will prompt for a password and we really don't want that. - if (passphrase.get() != nullptr) { - pass.data = static_cast(passphrase.get()); - } - pass.len = passphrase.size(); - } - return pass; -} -} // namespace - -EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePrivateKey( - const PrivateKeyEncodingConfig& config, - const Buffer& buffer) { - static constexpr auto keyOrError = [](EVPKeyPointer pkey, - bool had_passphrase = false) { - if (int err = ERR_peek_error()) { - if (ERR_GET_LIB(err) == ERR_LIB_PEM && - ERR_GET_REASON(err) == PEM_R_BAD_PASSWORD_READ && !had_passphrase) { - return ParseKeyResult(PKParseError::NEED_PASSPHRASE); - } - return ParseKeyResult(PKParseError::FAILED, err); - } - if (!pkey) return ParseKeyResult(PKParseError::FAILED); - return ParseKeyResult(std::move(pkey)); - }; - - auto bio = BIOPointer::New(buffer); - if (!bio) return ParseKeyResult(PKParseError::FAILED); - - auto passphrase = GetPassphrase(config); - - if (config.format == PKFormatType::PEM) { - auto key = PEM_read_bio_PrivateKey( - bio.get(), - nullptr, - PasswordCallback, - config.passphrase.has_value() ? &passphrase : nullptr); - return keyOrError(EVPKeyPointer(key), config.passphrase.has_value()); - } - - if (config.format != PKFormatType::DER) { - return ParseKeyResult(PKParseError::FAILED); - } - - switch (config.type) { - case PKEncodingType::PKCS1: { - auto key = d2i_PrivateKey_bio(bio.get(), nullptr); - return keyOrError(EVPKeyPointer(key)); - } - case PKEncodingType::PKCS8: { - if (IsEncryptedPrivateKeyInfo(buffer)) { - auto key = d2i_PKCS8PrivateKey_bio( - bio.get(), - nullptr, - PasswordCallback, - config.passphrase.has_value() ? &passphrase : nullptr); - return keyOrError(EVPKeyPointer(key), config.passphrase.has_value()); - } - - PKCS8Pointer p8inf(d2i_PKCS8_PRIV_KEY_INFO_bio(bio.get(), nullptr)); - if (!p8inf) { - return ParseKeyResult(PKParseError::FAILED, ERR_peek_error()); - } - return keyOrError(EVPKeyPointer(EVP_PKCS82PKEY(p8inf.get()))); - } - case PKEncodingType::SEC1: { - auto key = d2i_PrivateKey_bio(bio.get(), nullptr); - return keyOrError(EVPKeyPointer(key)); - } - default: { - return ParseKeyResult(PKParseError::FAILED, ERR_peek_error()); - } - }; -} - -Result EVPKeyPointer::writePrivateKey( - const PrivateKeyEncodingConfig& config) const { - if (config.format == PKFormatType::JWK) { - return Result(false); - } - - auto bio = BIOPointer::NewMem(); - if (!bio) { - return Result(false); - } - - auto passphrase = GetPassphrase(config); - MarkPopErrorOnReturn mark_pop_error_on_return; - bool err; - - switch (config.type) { - case PKEncodingType::PKCS1: { - // PKCS1 is only permitted for RSA keys. - if (id() != EVP_PKEY_RSA) return Result(false); - -#if OPENSSL_VERSION_MAJOR >= 3 - const RSA* rsa = EVP_PKEY_get0_RSA(get()); -#else - RSA* rsa = EVP_PKEY_get0_RSA(get()); -#endif - switch (config.format) { - case PKFormatType::PEM: { - err = PEM_write_bio_RSAPrivateKey( - bio.get(), - rsa, - config.cipher, - reinterpret_cast(passphrase.data), - passphrase.len, - nullptr, - nullptr) != 1; - break; - } - case PKFormatType::DER: { - // Encoding PKCS1 as DER. This variation does not permit encryption. - err = i2d_RSAPrivateKey_bio(bio.get(), rsa) != 1; - break; - } - default: { - // Should never get here. - return Result(false); - } - } - break; - } - case PKEncodingType::PKCS8: { - switch (config.format) { - case PKFormatType::PEM: { - // Encode PKCS#8 as PEM. - err = PEM_write_bio_PKCS8PrivateKey(bio.get(), - get(), - config.cipher, - passphrase.data, - passphrase.len, - nullptr, - nullptr) != 1; - break; - } - case PKFormatType::DER: { - err = i2d_PKCS8PrivateKey_bio(bio.get(), - get(), - config.cipher, - passphrase.data, - passphrase.len, - nullptr, - nullptr) != 1; - break; - } - default: { - // Should never get here. - return Result(false); - } - } - break; - } - case PKEncodingType::SEC1: { - // SEC1 is only permitted for EC keys - if (id() != EVP_PKEY_EC) return Result(false); - -#if OPENSSL_VERSION_MAJOR >= 3 - const EC_KEY* ec = EVP_PKEY_get0_EC_KEY(get()); -#else - EC_KEY* ec = EVP_PKEY_get0_EC_KEY(get()); -#endif - switch (config.format) { - case PKFormatType::PEM: { - err = PEM_write_bio_ECPrivateKey( - bio.get(), - ec, - config.cipher, - reinterpret_cast(passphrase.data), - passphrase.len, - nullptr, - nullptr) != 1; - break; - } - case PKFormatType::DER: { - // Encoding SEC1 as DER. This variation does not permit encryption. - err = i2d_ECPrivateKey_bio(bio.get(), ec) != 1; - break; - } - default: { - // Should never get here. - return Result(false); - } - } - break; - } - default: { - // Not a valid private key encoding - return Result(false); - } - } - - if (err) { - // Failed to encode the private key. - return Result(false, - mark_pop_error_on_return.peekError()); - } - - return bio; -} - -Result EVPKeyPointer::writePublicKey( - const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config) const { - auto bio = BIOPointer::NewMem(); - if (!bio) return Result(false); - - MarkPopErrorOnReturn mark_pop_error_on_return; - - if (config.type == ncrypto::EVPKeyPointer::PKEncodingType::PKCS1) { - // PKCS#1 is only valid for RSA keys. -#if OPENSSL_VERSION_MAJOR >= 3 - const RSA* rsa = EVP_PKEY_get0_RSA(get()); -#else - RSA* rsa = EVP_PKEY_get0_RSA(get()); -#endif - if (config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) { - // Encode PKCS#1 as PEM. - if (PEM_write_bio_RSAPublicKey(bio.get(), rsa) != 1) { - return Result(false, - mark_pop_error_on_return.peekError()); - } - return bio; - } - - // Encode PKCS#1 as DER. - if (i2d_RSAPublicKey_bio(bio.get(), rsa) != 1) { - return Result(false, - mark_pop_error_on_return.peekError()); - } - return bio; - } - - if (config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) { - // Encode SPKI as PEM. - if (PEM_write_bio_PUBKEY(bio.get(), get()) != 1) { - return Result(false, - mark_pop_error_on_return.peekError()); - } - return bio; - } - - // Encode SPKI as DER. - if (i2d_PUBKEY_bio(bio.get(), get()) != 1) { - return Result(false, - mark_pop_error_on_return.peekError()); - } - return bio; -} - -bool EVPKeyPointer::isRsaVariant() const { - if (!pkey_) return false; - int type = id(); - return type == EVP_PKEY_RSA || type == EVP_PKEY_RSA2 || - type == EVP_PKEY_RSA_PSS; -} - -bool EVPKeyPointer::isOneShotVariant() const { - if (!pkey_) return false; - int type = id(); - switch (type) { - case EVP_PKEY_ED25519: - case EVP_PKEY_ED448: -#if OPENSSL_WITH_PQC - case EVP_PKEY_ML_DSA_44: - case EVP_PKEY_ML_DSA_65: - case EVP_PKEY_ML_DSA_87: - case EVP_PKEY_SLH_DSA_SHA2_128F: - case EVP_PKEY_SLH_DSA_SHA2_128S: - case EVP_PKEY_SLH_DSA_SHA2_192F: - case EVP_PKEY_SLH_DSA_SHA2_192S: - case EVP_PKEY_SLH_DSA_SHA2_256F: - case EVP_PKEY_SLH_DSA_SHA2_256S: - case EVP_PKEY_SLH_DSA_SHAKE_128F: - case EVP_PKEY_SLH_DSA_SHAKE_128S: - case EVP_PKEY_SLH_DSA_SHAKE_192F: - case EVP_PKEY_SLH_DSA_SHAKE_192S: - case EVP_PKEY_SLH_DSA_SHAKE_256F: - case EVP_PKEY_SLH_DSA_SHAKE_256S: -#endif - return true; - default: - return false; - } -} - -bool EVPKeyPointer::isSigVariant() const { - if (!pkey_) return false; - int type = id(); - return type == EVP_PKEY_EC || type == EVP_PKEY_DSA; -} - -int EVPKeyPointer::getDefaultSignPadding() const { - return id() == EVP_PKEY_RSA_PSS ? RSA_PKCS1_PSS_PADDING : RSA_PKCS1_PADDING; -} - -std::optional EVPKeyPointer::getBytesOfRS() const { - if (!pkey_) return std::nullopt; - int bits, id = base_id(); - - if (id == EVP_PKEY_DSA) { - const DSA* dsa_key = EVP_PKEY_get0_DSA(get()); - // Both r and s are computed mod q, so their width is limited by that of q. - bits = BignumPointer::GetBitCount(DSA_get0_q(dsa_key)); - } else if (id == EVP_PKEY_EC) { - bits = EC_GROUP_order_bits(ECKeyPointer::GetGroup(*this)); - } else { - return std::nullopt; - } - - return (bits + 7) / 8; -} - -EVPKeyPointer::operator Rsa() const { - int type = id(); - if (type != EVP_PKEY_RSA && type != EVP_PKEY_RSA_PSS) return {}; - - // TODO(tniessen): Remove the "else" branch once we drop support for OpenSSL - // versions older than 1.1.1e via FIPS / dynamic linking. - OSSL3_CONST RSA* rsa; - if (OPENSSL_VERSION_NUMBER >= 0x1010105fL) { - rsa = EVP_PKEY_get0_RSA(get()); - } else { - rsa = static_cast(EVP_PKEY_get0(get())); - } - if (rsa == nullptr) return {}; - return Rsa(rsa); -} - -EVPKeyPointer::operator Dsa() const { - int type = id(); - if (type != EVP_PKEY_DSA) return {}; - - OSSL3_CONST DSA* dsa = EVP_PKEY_get0_DSA(get()); - if (dsa == nullptr) return {}; - return Dsa(dsa); -} - -bool EVPKeyPointer::validateDsaParameters() const { - if (!pkey_) return false; - /* Validate DSA2 parameters from FIPS 186-4 */ -#if OPENSSL_VERSION_MAJOR >= 3 - if (EVP_default_properties_is_fips_enabled(nullptr) && EVP_PKEY_DSA == id()) { -#else - if (FIPS_mode() && EVP_PKEY_DSA == id()) { -#endif - const DSA* dsa = EVP_PKEY_get0_DSA(pkey_.get()); - const BIGNUM* p; - const BIGNUM* q; - DSA_get0_pqg(dsa, &p, &q, nullptr); - int L = BignumPointer::GetBitCount(p); - int N = BignumPointer::GetBitCount(q); - - return (L == 1024 && N == 160) || (L == 2048 && N == 224) || - (L == 2048 && N == 256) || (L == 3072 && N == 256); - } - - return true; -} - -// ============================================================================ - -SSLPointer::SSLPointer(SSL* ssl) : ssl_(ssl) {} - -SSLPointer::SSLPointer(SSLPointer&& other) noexcept : ssl_(other.release()) {} - -SSLPointer& SSLPointer::operator=(SSLPointer&& other) noexcept { - if (this == &other) return *this; - this->~SSLPointer(); - return *new (this) SSLPointer(std::move(other)); -} - -SSLPointer::~SSLPointer() { - reset(); -} - -void SSLPointer::reset(SSL* ssl) { - ssl_.reset(ssl); -} - -SSL* SSLPointer::release() { - return ssl_.release(); -} - -SSLPointer SSLPointer::New(const SSLCtxPointer& ctx) { - if (!ctx) return {}; - return SSLPointer(SSL_new(ctx.get())); -} - -void SSLPointer::getCiphers(std::function cb) const { - if (!ssl_) return; - STACK_OF(SSL_CIPHER)* ciphers = SSL_get_ciphers(get()); - - // TLSv1.3 ciphers aren't listed by EVP. There are only 5, we could just - // document them, but since there are only 5, easier to just add them manually - // and not have to explain their absence in the API docs. They are lower-cased - // because the docs say they will be. - static constexpr const char* TLS13_CIPHERS[] = { - "tls_aes_256_gcm_sha384", - "tls_chacha20_poly1305_sha256", - "tls_aes_128_gcm_sha256", - "tls_aes_128_ccm_8_sha256", - "tls_aes_128_ccm_sha256"}; - - const int n = sk_SSL_CIPHER_num(ciphers); - - for (int i = 0; i < n; ++i) { - const SSL_CIPHER* cipher = sk_SSL_CIPHER_value(ciphers, i); - cb(SSL_CIPHER_get_name(cipher)); - } - - for (unsigned i = 0; i < 5; ++i) { - cb(TLS13_CIPHERS[i]); - } -} - -bool SSLPointer::setSession(const SSLSessionPointer& session) { - if (!session || !ssl_) return false; - return SSL_set_session(get(), session.get()) == 1; -} - -bool SSLPointer::setSniContext(const SSLCtxPointer& ctx) const { - if (!ctx) return false; - auto x509 = ncrypto::X509View::From(ctx); - if (!x509) return false; - EVP_PKEY* pkey = SSL_CTX_get0_privatekey(ctx.get()); - STACK_OF(X509) * chain; - int err = SSL_CTX_get0_chain_certs(ctx.get(), &chain); - if (err == 1) err = SSL_use_certificate(get(), x509); - if (err == 1) err = SSL_use_PrivateKey(get(), pkey); - if (err == 1 && chain != nullptr) err = SSL_set1_chain(get(), chain); - return err == 1; -} - -std::optional SSLPointer::verifyPeerCertificate() const { - if (!ssl_) return std::nullopt; - if (X509Pointer::PeerFrom(*this)) { - return SSL_get_verify_result(get()); - } - - const SSL_CIPHER* curr_cipher = SSL_get_current_cipher(get()); - const SSL_SESSION* sess = SSL_get_session(get()); - // Allow no-cert for PSK authentication in TLS1.2 and lower. - // In TLS1.3 check that session was reused because TLS1.3 PSK - // looks like session resumption. - if (SSL_CIPHER_get_auth_nid(curr_cipher) == NID_auth_psk || - (SSL_SESSION_get_protocol_version(sess) == TLS1_3_VERSION && - SSL_session_reused(get()))) { - return X509_V_OK; - } - - return std::nullopt; -} - -const char* SSLPointer::getClientHelloAlpn() const { - if (ssl_ == nullptr) return {}; -#ifndef OPENSSL_IS_BORINGSSL - const unsigned char* buf; - size_t len; - size_t rem; - - if (!SSL_client_hello_get0_ext( - get(), - TLSEXT_TYPE_application_layer_protocol_negotiation, - &buf, - &rem) || - rem < 2) { - return {}; - } - - len = (buf[0] << 8) | buf[1]; - if (len + 2 != rem) return {}; - return reinterpret_cast(buf + 3); -#else - // Boringssl doesn't have a public API for this. - return {}; -#endif -} - -const char* SSLPointer::getClientHelloServerName() const { - if (ssl_ == nullptr) return {}; -#ifndef OPENSSL_IS_BORINGSSL - const unsigned char* buf; - size_t len; - size_t rem; - - if (!SSL_client_hello_get0_ext(get(), TLSEXT_TYPE_server_name, &buf, &rem) || - rem <= 2) { - return {}; - } - - len = (*buf << 8) | *(buf + 1); - if (len + 2 != rem) return {}; - rem = len; - - if (rem == 0 || *(buf + 2) != TLSEXT_NAMETYPE_host_name) return {}; - rem--; - if (rem <= 2) return {}; - len = (*(buf + 3) << 8) | *(buf + 4); - if (len + 2 > rem) return {}; - return reinterpret_cast(buf + 5); -#else - // Boringssl doesn't have a public API for this. - return {}; -#endif -} - -std::optional SSLPointer::GetServerName( - const SSL* ssl) { - if (ssl == nullptr) return std::nullopt; - auto res = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); - if (res == nullptr) return std::nullopt; - return res; -} - -std::optional SSLPointer::getServerName() const { - if (!ssl_) return std::nullopt; - return GetServerName(get()); -} - -X509View SSLPointer::getCertificate() const { - if (!ssl_) return {}; - ClearErrorOnReturn clear_error_on_return; - return ncrypto::X509View(SSL_get_certificate(get())); -} - -const SSL_CIPHER* SSLPointer::getCipher() const { - if (!ssl_) return nullptr; - return SSL_get_current_cipher(get()); -} - -bool SSLPointer::isServer() const { - return SSL_is_server(get()) != 0; -} - -EVPKeyPointer SSLPointer::getPeerTempKey() const { - if (!ssl_) return {}; - EVP_PKEY* raw_key = nullptr; -#ifndef OPENSSL_IS_BORINGSSL - if (!SSL_get_peer_tmp_key(get(), &raw_key)) return {}; -#else - if (!SSL_get_server_tmp_key(get(), &raw_key)) return {}; -#endif - return EVPKeyPointer(raw_key); -} - -std::optional SSLPointer::getCipherName() const { - auto cipher = getCipher(); - if (cipher == nullptr) return std::nullopt; - return SSL_CIPHER_get_name(cipher); -} - -std::optional SSLPointer::getCipherStandardName() const { - auto cipher = getCipher(); - if (cipher == nullptr) return std::nullopt; - return SSL_CIPHER_standard_name(cipher); -} - -std::optional SSLPointer::getCipherVersion() const { - auto cipher = getCipher(); - if (cipher == nullptr) return std::nullopt; - return SSL_CIPHER_get_version(cipher); -} - -std::optional SSLPointer::getSecurityLevel() { -#ifndef OPENSSL_IS_BORINGSSL - auto ctx = SSLCtxPointer::New(); - if (!ctx) return std::nullopt; - - auto ssl = SSLPointer::New(ctx); - if (!ssl) return std::nullopt; - - return SSL_get_security_level(ssl); -#else - // OPENSSL_TLS_SECURITY_LEVEL is not defined in BoringSSL - // so assume it is the default OPENSSL_TLS_SECURITY_LEVEL value. - return 1; -#endif // OPENSSL_IS_BORINGSSL -} - -SSLCtxPointer::SSLCtxPointer(SSL_CTX* ctx) : ctx_(ctx) {} - -SSLCtxPointer::SSLCtxPointer(SSLCtxPointer&& other) noexcept - : ctx_(other.release()) {} - -SSLCtxPointer& SSLCtxPointer::operator=(SSLCtxPointer&& other) noexcept { - if (this == &other) return *this; - this->~SSLCtxPointer(); - return *new (this) SSLCtxPointer(std::move(other)); -} - -SSLCtxPointer::~SSLCtxPointer() { - reset(); -} - -void SSLCtxPointer::reset(SSL_CTX* ctx) { - ctx_.reset(ctx); -} - -void SSLCtxPointer::reset(const SSL_METHOD* method) { - ctx_.reset(SSL_CTX_new(method)); -} - -SSL_CTX* SSLCtxPointer::release() { - return ctx_.release(); -} - -SSLCtxPointer SSLCtxPointer::NewServer() { - return SSLCtxPointer(SSL_CTX_new(TLS_server_method())); -} - -SSLCtxPointer SSLCtxPointer::NewClient() { - return SSLCtxPointer(SSL_CTX_new(TLS_client_method())); -} - -SSLCtxPointer SSLCtxPointer::New(const SSL_METHOD* method) { - return SSLCtxPointer(SSL_CTX_new(method)); -} - -bool SSLCtxPointer::setGroups(const char* groups) { - return SSL_CTX_set1_groups_list(get(), groups) == 1; -} - -bool SSLCtxPointer::setCipherSuites(const char* ciphers) { -#ifndef OPENSSL_IS_BORINGSSL - if (!ctx_) return false; - return SSL_CTX_set_ciphersuites(ctx_.get(), ciphers); -#else - // BoringSSL does not allow API config of TLS 1.3 cipher suites. - // We treat this as a non-op. - return true; -#endif -} - -// ============================================================================ - -const Cipher Cipher::FromName(const char* name) { - return Cipher(EVP_get_cipherbyname(name)); -} - -const Cipher Cipher::FromNid(int nid) { - return Cipher(EVP_get_cipherbynid(nid)); -} - -const Cipher Cipher::FromCtx(const CipherCtxPointer& ctx) { - return Cipher(EVP_CIPHER_CTX_cipher(ctx.get())); -} - -const Cipher Cipher::EMPTY = Cipher(); -const Cipher Cipher::AES_128_CBC = Cipher::FromNid(NID_aes_128_cbc); -const Cipher Cipher::AES_192_CBC = Cipher::FromNid(NID_aes_192_cbc); -const Cipher Cipher::AES_256_CBC = Cipher::FromNid(NID_aes_256_cbc); -const Cipher Cipher::AES_128_CTR = Cipher::FromNid(NID_aes_128_ctr); -const Cipher Cipher::AES_192_CTR = Cipher::FromNid(NID_aes_192_ctr); -const Cipher Cipher::AES_256_CTR = Cipher::FromNid(NID_aes_256_ctr); -const Cipher Cipher::AES_128_GCM = Cipher::FromNid(NID_aes_128_gcm); -const Cipher Cipher::AES_192_GCM = Cipher::FromNid(NID_aes_192_gcm); -const Cipher Cipher::AES_256_GCM = Cipher::FromNid(NID_aes_256_gcm); -const Cipher Cipher::AES_128_KW = Cipher::FromNid(NID_id_aes128_wrap); -const Cipher Cipher::AES_192_KW = Cipher::FromNid(NID_id_aes192_wrap); -const Cipher Cipher::AES_256_KW = Cipher::FromNid(NID_id_aes256_wrap); -const Cipher Cipher::AES_128_OCB = Cipher::FromNid(NID_aes_128_ocb); -const Cipher Cipher::AES_192_OCB = Cipher::FromNid(NID_aes_192_ocb); -const Cipher Cipher::AES_256_OCB = Cipher::FromNid(NID_aes_256_ocb); -const Cipher Cipher::CHACHA20_POLY1305 = Cipher::FromNid(NID_chacha20_poly1305); - -bool Cipher::isGcmMode() const { - if (!cipher_) return false; - return getMode() == EVP_CIPH_GCM_MODE; -} - -bool Cipher::isWrapMode() const { - if (!cipher_) return false; - return getMode() == EVP_CIPH_WRAP_MODE; -} - -bool Cipher::isCtrMode() const { - if (!cipher_) return false; - return getMode() == EVP_CIPH_CTR_MODE; -} - -bool Cipher::isCcmMode() const { - if (!cipher_) return false; - return getMode() == EVP_CIPH_CCM_MODE; -} - -bool Cipher::isOcbMode() const { - if (!cipher_) return false; - return getMode() == EVP_CIPH_OCB_MODE; -} - -bool Cipher::isStreamMode() const { - if (!cipher_) return false; - return getMode() == EVP_CIPH_STREAM_CIPHER; -} - -bool Cipher::isChaCha20Poly1305() const { - if (!cipher_) return false; - return getNid() == NID_chacha20_poly1305; -} - -int Cipher::getMode() const { - if (!cipher_) return 0; - return EVP_CIPHER_mode(cipher_); -} - -int Cipher::getIvLength() const { - if (!cipher_) return 0; - return EVP_CIPHER_iv_length(cipher_); -} - -int Cipher::getKeyLength() const { - if (!cipher_) return 0; - return EVP_CIPHER_key_length(cipher_); -} - -int Cipher::getBlockSize() const { - if (!cipher_) return 0; - return EVP_CIPHER_block_size(cipher_); -} - -int Cipher::getNid() const { - if (!cipher_) return 0; - return EVP_CIPHER_nid(cipher_); -} - -std::string_view Cipher::getModeLabel() const { - if (!cipher_) return {}; - switch (getMode()) { - case EVP_CIPH_CCM_MODE: - return "ccm"; - case EVP_CIPH_CFB_MODE: - return "cfb"; - case EVP_CIPH_CBC_MODE: - return "cbc"; - case EVP_CIPH_CTR_MODE: - return "ctr"; - case EVP_CIPH_ECB_MODE: - return "ecb"; - case EVP_CIPH_GCM_MODE: - return "gcm"; - case EVP_CIPH_OCB_MODE: - return "ocb"; - case EVP_CIPH_OFB_MODE: - return "ofb"; - case EVP_CIPH_WRAP_MODE: - return "wrap"; - case EVP_CIPH_XTS_MODE: - return "xts"; - case EVP_CIPH_STREAM_CIPHER: - return "stream"; - } - return "{unknown}"; -} - -const char* Cipher::getName() const { - if (!cipher_) return {}; - // OBJ_nid2sn(EVP_CIPHER_nid(cipher)) is used here instead of - // EVP_CIPHER_name(cipher) for compatibility with BoringSSL. - return OBJ_nid2sn(getNid()); -} - -bool Cipher::isSupportedAuthenticatedMode() const { - switch (getMode()) { - case EVP_CIPH_CCM_MODE: - case EVP_CIPH_GCM_MODE: -#ifndef OPENSSL_NO_OCB - case EVP_CIPH_OCB_MODE: -#endif - return true; - case EVP_CIPH_STREAM_CIPHER: - return getNid() == NID_chacha20_poly1305; - default: - return false; - } -} - -int Cipher::bytesToKey(const Digest& digest, - const Buffer& input, - unsigned char* key, - unsigned char* iv) const { - return EVP_BytesToKey( - *this, Digest::MD5, nullptr, input.data, input.len, 1, key, iv); -} - -// ============================================================================ - -CipherCtxPointer CipherCtxPointer::New() { - auto ret = CipherCtxPointer(EVP_CIPHER_CTX_new()); - if (!ret) return {}; - EVP_CIPHER_CTX_init(ret.get()); - return ret; -} - -CipherCtxPointer::CipherCtxPointer(EVP_CIPHER_CTX* ctx) : ctx_(ctx) {} - -CipherCtxPointer::CipherCtxPointer(CipherCtxPointer&& other) noexcept - : ctx_(other.release()) {} - -CipherCtxPointer& CipherCtxPointer::operator=( - CipherCtxPointer&& other) noexcept { - if (this == &other) return *this; - this->~CipherCtxPointer(); - return *new (this) CipherCtxPointer(std::move(other)); -} - -CipherCtxPointer::~CipherCtxPointer() { - reset(); -} - -void CipherCtxPointer::reset(EVP_CIPHER_CTX* ctx) { - ctx_.reset(ctx); -} - -EVP_CIPHER_CTX* CipherCtxPointer::release() { - return ctx_.release(); -} - -void CipherCtxPointer::setAllowWrap() { - if (!ctx_) return; - EVP_CIPHER_CTX_set_flags(ctx_.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); -} - -bool CipherCtxPointer::setKeyLength(size_t length) { - if (!ctx_) return false; - return EVP_CIPHER_CTX_set_key_length(ctx_.get(), length); -} - -bool CipherCtxPointer::setIvLength(size_t length) { - if (!ctx_) return false; - return EVP_CIPHER_CTX_ctrl( - ctx_.get(), EVP_CTRL_AEAD_SET_IVLEN, length, nullptr); -} - -bool CipherCtxPointer::setAeadTag(const Buffer& tag) { - if (!ctx_) return false; - return EVP_CIPHER_CTX_ctrl( - ctx_.get(), EVP_CTRL_AEAD_SET_TAG, tag.len, const_cast(tag.data)); -} - -bool CipherCtxPointer::setAeadTagLength(size_t length) { - if (!ctx_) return false; - return EVP_CIPHER_CTX_ctrl( - ctx_.get(), EVP_CTRL_AEAD_SET_TAG, length, nullptr); -} - -bool CipherCtxPointer::setPadding(bool padding) { - if (!ctx_) return false; - return EVP_CIPHER_CTX_set_padding(ctx_.get(), padding); -} - -int CipherCtxPointer::getBlockSize() const { - if (!ctx_) return 0; - return EVP_CIPHER_CTX_block_size(ctx_.get()); -} - -int CipherCtxPointer::getMode() const { - if (!ctx_) return 0; - return EVP_CIPHER_CTX_mode(ctx_.get()); -} - -bool CipherCtxPointer::isGcmMode() const { - if (!ctx_) return false; - return getMode() == EVP_CIPH_GCM_MODE; -} - -bool CipherCtxPointer::isOcbMode() const { - if (!ctx_) return false; - return getMode() == EVP_CIPH_OCB_MODE; -} - -bool CipherCtxPointer::isCcmMode() const { - if (!ctx_) return false; - return getMode() == EVP_CIPH_CCM_MODE; -} - -bool CipherCtxPointer::isWrapMode() const { - if (!ctx_) return false; - return getMode() == EVP_CIPH_WRAP_MODE; -} - -bool CipherCtxPointer::isChaCha20Poly1305() const { - if (!ctx_) return false; - return getNid() == NID_chacha20_poly1305; -} - -int CipherCtxPointer::getNid() const { - if (!ctx_) return 0; - return EVP_CIPHER_CTX_nid(ctx_.get()); -} - -bool CipherCtxPointer::init(const Cipher& cipher, - bool encrypt, - const unsigned char* key, - const unsigned char* iv) { - if (!ctx_) return false; - return EVP_CipherInit_ex( - ctx_.get(), cipher, nullptr, key, iv, encrypt ? 1 : 0) == 1; -} - -bool CipherCtxPointer::update(const Buffer& in, - unsigned char* out, - int* out_len, - bool finalize) { - if (!ctx_) return false; - if (!finalize) { - return EVP_CipherUpdate(ctx_.get(), out, out_len, in.data, in.len) == 1; - } - return EVP_CipherFinal_ex(ctx_.get(), out, out_len) == 1; -} - -bool CipherCtxPointer::getAeadTag(size_t len, unsigned char* out) { - if (!ctx_) return false; - return EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_GET_TAG, len, out); -} - -// ============================================================================ - -ECDSASigPointer::ECDSASigPointer() : sig_(nullptr) {} -ECDSASigPointer::ECDSASigPointer(ECDSA_SIG* sig) : sig_(sig) { - if (sig_) { - ECDSA_SIG_get0(sig_.get(), &pr_, &ps_); - } -} -ECDSASigPointer::ECDSASigPointer(ECDSASigPointer&& other) noexcept - : sig_(other.release()) { - if (sig_) { - ECDSA_SIG_get0(sig_.get(), &pr_, &ps_); - } -} - -ECDSASigPointer& ECDSASigPointer::operator=(ECDSASigPointer&& other) noexcept { - sig_.reset(other.release()); - if (sig_) { - ECDSA_SIG_get0(sig_.get(), &pr_, &ps_); - } - return *this; -} - -ECDSASigPointer::~ECDSASigPointer() { - reset(); -} - -void ECDSASigPointer::reset(ECDSA_SIG* sig) { - sig_.reset(); - pr_ = nullptr; - ps_ = nullptr; -} - -ECDSA_SIG* ECDSASigPointer::release() { - pr_ = nullptr; - ps_ = nullptr; - return sig_.release(); -} - -ECDSASigPointer ECDSASigPointer::New() { - return ECDSASigPointer(ECDSA_SIG_new()); -} - -ECDSASigPointer ECDSASigPointer::Parse(const Buffer& sig) { - const unsigned char* ptr = sig.data; - return ECDSASigPointer(d2i_ECDSA_SIG(nullptr, &ptr, sig.len)); -} - -bool ECDSASigPointer::setParams(BignumPointer&& r, BignumPointer&& s) { - if (!sig_) return false; - return ECDSA_SIG_set0(sig_.get(), r.release(), s.release()); -} - -Buffer ECDSASigPointer::encode() const { - if (!sig_) - return { - .data = nullptr, - .len = 0, - }; - Buffer buf; - buf.len = i2d_ECDSA_SIG(sig_.get(), &buf.data); - return buf; -} - -// ============================================================================ - -ECGroupPointer::ECGroupPointer() : group_(nullptr) {} - -ECGroupPointer::ECGroupPointer(EC_GROUP* group) : group_(group) {} - -ECGroupPointer::ECGroupPointer(ECGroupPointer&& other) noexcept - : group_(other.release()) {} - -ECGroupPointer& ECGroupPointer::operator=(ECGroupPointer&& other) noexcept { - group_.reset(other.release()); - return *this; -} - -ECGroupPointer::~ECGroupPointer() { - reset(); -} - -void ECGroupPointer::reset(EC_GROUP* group) { - group_.reset(); -} - -EC_GROUP* ECGroupPointer::release() { - return group_.release(); -} - -ECGroupPointer ECGroupPointer::NewByCurveName(int nid) { - return ECGroupPointer(EC_GROUP_new_by_curve_name(nid)); -} - -// ============================================================================ - -ECPointPointer::ECPointPointer() : point_(nullptr) {} - -ECPointPointer::ECPointPointer(EC_POINT* point) : point_(point) {} - -ECPointPointer::ECPointPointer(ECPointPointer&& other) noexcept - : point_(other.release()) {} - -ECPointPointer& ECPointPointer::operator=(ECPointPointer&& other) noexcept { - point_.reset(other.release()); - return *this; -} - -ECPointPointer::~ECPointPointer() { - reset(); -} - -void ECPointPointer::reset(EC_POINT* point) { - point_.reset(point); -} - -EC_POINT* ECPointPointer::release() { - return point_.release(); -} - -ECPointPointer ECPointPointer::New(const EC_GROUP* group) { - return ECPointPointer(EC_POINT_new(group)); -} - -bool ECPointPointer::setFromBuffer(const Buffer& buffer, - const EC_GROUP* group) { - if (!point_) return false; - return EC_POINT_oct2point( - group, point_.get(), buffer.data, buffer.len, nullptr); -} - -bool ECPointPointer::mul(const EC_GROUP* group, const BIGNUM* priv_key) { - if (!point_) return false; - return EC_POINT_mul(group, point_.get(), priv_key, nullptr, nullptr, nullptr); -} - -// ============================================================================ - -ECKeyPointer::ECKeyPointer() : key_(nullptr) {} - -ECKeyPointer::ECKeyPointer(EC_KEY* key) : key_(key) {} - -ECKeyPointer::ECKeyPointer(ECKeyPointer&& other) noexcept - : key_(other.release()) {} - -ECKeyPointer& ECKeyPointer::operator=(ECKeyPointer&& other) noexcept { - key_.reset(other.release()); - return *this; -} - -ECKeyPointer::~ECKeyPointer() { - reset(); -} - -void ECKeyPointer::reset(EC_KEY* key) { - key_.reset(key); -} - -EC_KEY* ECKeyPointer::release() { - return key_.release(); -} - -ECKeyPointer ECKeyPointer::clone() const { - if (!key_) return {}; - return ECKeyPointer(EC_KEY_dup(key_.get())); -} - -bool ECKeyPointer::generate() { - if (!key_) return false; - return EC_KEY_generate_key(key_.get()); -} - -bool ECKeyPointer::setPublicKey(const ECPointPointer& pub) { - if (!key_) return false; - return EC_KEY_set_public_key(key_.get(), pub.get()) == 1; -} - -bool ECKeyPointer::setPublicKeyRaw(const BignumPointer& x, - const BignumPointer& y) { - if (!key_) return false; - return EC_KEY_set_public_key_affine_coordinates( - key_.get(), x.get(), y.get()) == 1; -} - -bool ECKeyPointer::setPrivateKey(const BignumPointer& priv) { - if (!key_) return false; - return EC_KEY_set_private_key(key_.get(), priv.get()) == 1; -} - -const BIGNUM* ECKeyPointer::getPrivateKey() const { - if (!key_) return nullptr; - return GetPrivateKey(key_.get()); -} - -const BIGNUM* ECKeyPointer::GetPrivateKey(const EC_KEY* key) { - return EC_KEY_get0_private_key(key); -} - -const EC_POINT* ECKeyPointer::getPublicKey() const { - if (!key_) return nullptr; - return GetPublicKey(key_.get()); -} - -const EC_POINT* ECKeyPointer::GetPublicKey(const EC_KEY* key) { - return EC_KEY_get0_public_key(key); -} - -const EC_GROUP* ECKeyPointer::getGroup() const { - if (!key_) return nullptr; - return GetGroup(key_.get()); -} - -const EC_GROUP* ECKeyPointer::GetGroup(const EC_KEY* key) { - return EC_KEY_get0_group(key); -} - -int ECKeyPointer::GetGroupName(const EC_KEY* key) { - const EC_GROUP* group = GetGroup(key); - return group ? EC_GROUP_get_curve_name(group) : 0; -} - -bool ECKeyPointer::Check(const EC_KEY* key) { - return EC_KEY_check_key(key) == 1; -} - -bool ECKeyPointer::checkKey() const { - if (!key_) return false; - return Check(key_.get()); -} - -ECKeyPointer ECKeyPointer::NewByCurveName(int nid) { - return ECKeyPointer(EC_KEY_new_by_curve_name(nid)); -} - -ECKeyPointer ECKeyPointer::New(const EC_GROUP* group) { - auto ptr = ECKeyPointer(EC_KEY_new()); - if (!ptr) return {}; - if (!EC_KEY_set_group(ptr.get(), group)) return {}; - return ptr; -} - -// ============================================================================ - -EVPKeyCtxPointer::EVPKeyCtxPointer() : ctx_(nullptr) {} - -EVPKeyCtxPointer::EVPKeyCtxPointer(EVP_PKEY_CTX* ctx) : ctx_(ctx) {} - -EVPKeyCtxPointer::EVPKeyCtxPointer(EVPKeyCtxPointer&& other) noexcept - : ctx_(other.release()) {} - -EVPKeyCtxPointer& EVPKeyCtxPointer::operator=( - EVPKeyCtxPointer&& other) noexcept { - ctx_.reset(other.release()); - return *this; -} - -EVPKeyCtxPointer::~EVPKeyCtxPointer() { - reset(); -} - -void EVPKeyCtxPointer::reset(EVP_PKEY_CTX* ctx) { - ctx_.reset(ctx); -} - -EVP_PKEY_CTX* EVPKeyCtxPointer::release() { - return ctx_.release(); -} - -EVPKeyCtxPointer EVPKeyCtxPointer::New(const EVPKeyPointer& key) { - if (!key) return {}; - return EVPKeyCtxPointer(EVP_PKEY_CTX_new(key.get(), nullptr)); -} - -EVPKeyCtxPointer EVPKeyCtxPointer::NewFromID(int id) { -#ifdef OPENSSL_IS_BORINGSSL - // DSA keys are not supported with BoringSSL - if (id == EVP_PKEY_DSA) return {}; -#endif - return EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(id, nullptr)); -} - -bool EVPKeyCtxPointer::initForDerive(const EVPKeyPointer& peer) { - if (!ctx_) return false; - if (EVP_PKEY_derive_init(ctx_.get()) != 1) return false; - return EVP_PKEY_derive_set_peer(ctx_.get(), peer.get()) == 1; -} - -bool EVPKeyCtxPointer::initForKeygen() { - if (!ctx_) return false; - return EVP_PKEY_keygen_init(ctx_.get()) == 1; -} - -bool EVPKeyCtxPointer::initForParamgen() { - if (!ctx_) return false; - return EVP_PKEY_paramgen_init(ctx_.get()) == 1; -} - -int EVPKeyCtxPointer::initForVerify() { - if (!ctx_) return 0; - return EVP_PKEY_verify_init(ctx_.get()); -} - -int EVPKeyCtxPointer::initForSign() { - if (!ctx_) return 0; - return EVP_PKEY_sign_init(ctx_.get()); -} - -bool EVPKeyCtxPointer::setDhParameters(int prime_size, uint32_t generator) { -#ifndef OPENSSL_IS_BORINGSSL - if (!ctx_) return false; - return EVP_PKEY_CTX_set_dh_paramgen_prime_len(ctx_.get(), prime_size) == 1 && - EVP_PKEY_CTX_set_dh_paramgen_generator(ctx_.get(), generator) == 1; -#else - // TODO(jasnell): Boringssl appears not to support this operation. - // Is there an alternative approach that Boringssl does support? - return false; -#endif -} - -bool EVPKeyCtxPointer::setDsaParameters(uint32_t bits, - std::optional q_bits) { - if (!ctx_) return false; - if (EVP_PKEY_CTX_set_dsa_paramgen_bits(ctx_.get(), bits) != 1) { - return false; - } - if (q_bits.has_value() && - EVP_PKEY_CTX_set_dsa_paramgen_q_bits(ctx_.get(), q_bits.value()) != 1) { - return false; - } - return true; -} - -bool EVPKeyCtxPointer::setEcParameters(int curve, int encoding) { - if (!ctx_) return false; - return EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx_.get(), curve) == 1 && - EVP_PKEY_CTX_set_ec_param_enc(ctx_.get(), encoding) == 1; -} - -bool EVPKeyCtxPointer::setRsaOaepMd(const Digest& md) { - if (!md || !ctx_) return false; - const EVP_MD* md_ptr = md; - return EVP_PKEY_CTX_set_rsa_oaep_md(ctx_.get(), md_ptr) > 0; -} - -bool EVPKeyCtxPointer::setRsaMgf1Md(const Digest& md) { - if (!md || !ctx_) return false; - const EVP_MD* md_ptr = md; - return EVP_PKEY_CTX_set_rsa_mgf1_md(ctx_.get(), md_ptr) > 0; -} - -bool EVPKeyCtxPointer::setRsaPadding(int padding) { - return setRsaPadding(ctx_.get(), padding, std::nullopt); -} - -bool EVPKeyCtxPointer::setRsaPadding(EVP_PKEY_CTX* ctx, - int padding, - std::optional salt_len) { - if (ctx == nullptr) return false; - if (EVP_PKEY_CTX_set_rsa_padding(ctx, padding) <= 0) { - return false; - } - if (padding == RSA_PKCS1_PSS_PADDING && salt_len.has_value()) { - return EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, salt_len.value()) > 0; - } - return true; -} - -bool EVPKeyCtxPointer::setRsaKeygenBits(int bits) { - if (!ctx_) return false; - return EVP_PKEY_CTX_set_rsa_keygen_bits(ctx_.get(), bits) == 1; -} - -bool EVPKeyCtxPointer::setRsaKeygenPubExp(BignumPointer&& e) { - if (!ctx_) return false; - if (EVP_PKEY_CTX_set_rsa_keygen_pubexp(ctx_.get(), e.get()) == 1) { - // The ctx_ takes ownership of e on success. - e.release(); - return true; - } - return false; -} - -bool EVPKeyCtxPointer::setRsaPssKeygenMd(const Digest& md) { - if (!md || !ctx_) return false; - // OpenSSL < 3 accepts a void* for the md parameter. - const EVP_MD* md_ptr = md; - return EVP_PKEY_CTX_set_rsa_pss_keygen_md(ctx_.get(), md_ptr) > 0; -} - -bool EVPKeyCtxPointer::setRsaPssKeygenMgf1Md(const Digest& md) { - if (!md || !ctx_) return false; - const EVP_MD* md_ptr = md; - return EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md(ctx_.get(), md_ptr) > 0; -} - -bool EVPKeyCtxPointer::setRsaPssSaltlen(int salt_len) { - if (!ctx_) return false; - return EVP_PKEY_CTX_set_rsa_pss_keygen_saltlen(ctx_.get(), salt_len) > 0; -} - -bool EVPKeyCtxPointer::setRsaImplicitRejection() { -#ifndef OPENSSL_IS_BORINGSSL - if (!ctx_) return false; - return EVP_PKEY_CTX_ctrl_str( - ctx_.get(), "rsa_pkcs1_implicit_rejection", "1") > 0; - // From the doc -2 means that the option is not supported. - // The default for the option is enabled and if it has been - // specifically disabled we want to respect that so we will - // not throw an error if the option is supported regardless - // of how it is set. The call to set the value - // will not affect what is used since a different context is - // used in the call if the option is supported -#else - // TODO(jasnell): Boringssl appears not to support this operation. - // Is there an alternative approach that Boringssl does support? - return true; -#endif -} - -bool EVPKeyCtxPointer::setRsaOaepLabel(DataPointer&& data) { - if (!ctx_) return false; - if (EVP_PKEY_CTX_set0_rsa_oaep_label(ctx_.get(), - static_cast(data.get()), - data.size()) > 0) { - // The ctx_ takes ownership of data on success. - data.release(); - return true; - } - return false; -} - -bool EVPKeyCtxPointer::setSignatureMd(const EVPMDCtxPointer& md) { - if (!ctx_) return false; - return EVP_PKEY_CTX_set_signature_md(ctx_.get(), EVP_MD_CTX_md(md.get())) == - 1; -} - -bool EVPKeyCtxPointer::initForEncrypt() { - if (!ctx_) return false; - return EVP_PKEY_encrypt_init(ctx_.get()) == 1; -} - -bool EVPKeyCtxPointer::initForDecrypt() { - if (!ctx_) return false; - return EVP_PKEY_decrypt_init(ctx_.get()) == 1; -} - -DataPointer EVPKeyCtxPointer::derive() const { - if (!ctx_) return {}; - size_t len = 0; - if (EVP_PKEY_derive(ctx_.get(), nullptr, &len) != 1) return {}; - auto data = DataPointer::Alloc(len); - if (!data) return {}; - if (EVP_PKEY_derive( - ctx_.get(), static_cast(data.get()), &len) != 1) { - return {}; - } - return data; -} - -EVPKeyPointer EVPKeyCtxPointer::paramgen() const { - if (!ctx_) return {}; - EVP_PKEY* key = nullptr; - if (EVP_PKEY_paramgen(ctx_.get(), &key) != 1) return {}; - return EVPKeyPointer(key); -} - -bool EVPKeyCtxPointer::publicCheck() const { - if (!ctx_) return false; -#ifndef OPENSSL_IS_BORINGSSL -#if OPENSSL_VERSION_MAJOR >= 3 - return EVP_PKEY_public_check_quick(ctx_.get()) == 1; -#else - return EVP_PKEY_public_check(ctx_.get()) == 1; -#endif -#else // OPENSSL_IS_BORINGSSL - // Boringssl appears not to support this operation. - // TODO(jasnell): Is there an alternative approach that Boringssl does - // support? - return true; -#endif -} - -bool EVPKeyCtxPointer::privateCheck() const { - if (!ctx_) return false; -#ifndef OPENSSL_IS_BORINGSSL - return EVP_PKEY_check(ctx_.get()) == 1; -#else - // Boringssl appears not to support this operation. - // TODO(jasnell): Is there an alternative approach that Boringssl does - // support? - return true; -#endif -} - -bool EVPKeyCtxPointer::verify(const Buffer& sig, - const Buffer& data) { - if (!ctx_) return false; - return EVP_PKEY_verify(ctx_.get(), sig.data, sig.len, data.data, data.len) == - 1; -} - -DataPointer EVPKeyCtxPointer::sign(const Buffer& data) { - if (!ctx_) return {}; - size_t len = 0; - if (EVP_PKEY_sign(ctx_.get(), nullptr, &len, data.data, data.len) != 1) { - return {}; - } - auto buf = DataPointer::Alloc(len); - if (!buf) return {}; - if (EVP_PKEY_sign(ctx_.get(), - static_cast(buf.get()), - &len, - data.data, - data.len) != 1) { - return {}; - } - return buf.resize(len); -} - -bool EVPKeyCtxPointer::signInto(const Buffer& data, - Buffer* sig) { - if (!ctx_) return false; - size_t len = sig->len; - if (EVP_PKEY_sign(ctx_.get(), sig->data, &len, data.data, data.len) != 1) { - return false; - } - sig->len = len; - return true; -} - -// ============================================================================ - -namespace { - -using EVP_PKEY_cipher_init_t = int(EVP_PKEY_CTX* ctx); -using EVP_PKEY_cipher_t = int(EVP_PKEY_CTX* ctx, - unsigned char* out, - size_t* outlen, - const unsigned char* in, - size_t inlen); - -template -DataPointer RSA_Cipher(const EVPKeyPointer& key, - const Rsa::CipherParams& params, - const Buffer in) { - if (!key) return {}; - EVPKeyCtxPointer ctx = key.newCtx(); - - if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) || - (params.digest != nullptr && (!ctx.setRsaOaepMd(params.digest) || - !ctx.setRsaMgf1Md(params.digest)))) { - return {}; - } - - if (params.label.len != 0 && params.label.data != nullptr && - !ctx.setRsaOaepLabel(DataPointer::Copy(params.label))) { - return {}; - } - - size_t out_len = 0; - if (cipher(ctx.get(), - nullptr, - &out_len, - reinterpret_cast(in.data), - in.len) <= 0) { - return {}; - } - - auto buf = DataPointer::Alloc(out_len); - if (!buf) return {}; - - if (cipher(ctx.get(), - static_cast(buf.get()), - &out_len, - static_cast(in.data), - in.len) <= 0) { - return {}; - } - - return buf.resize(out_len); -} - -template -DataPointer CipherImpl(const EVPKeyPointer& key, - const Rsa::CipherParams& params, - const Buffer in) { - if (!key) return {}; - EVPKeyCtxPointer ctx = key.newCtx(); - if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) || - (params.digest != nullptr && !ctx.setRsaOaepMd(params.digest))) { - return {}; - } - - if (params.label.len != 0 && params.label.data != nullptr && - !ctx.setRsaOaepLabel(DataPointer::Copy(params.label))) { - return {}; - } - - size_t out_len = 0; - if (cipher(ctx.get(), - nullptr, - &out_len, - static_cast(in.data), - in.len) <= 0) { - return {}; - } - - auto buf = DataPointer::Alloc(out_len); - if (!buf) return {}; - - if (cipher(ctx.get(), - static_cast(buf.get()), - &out_len, - static_cast(in.data), - in.len) <= 0) { - return {}; - } - - return buf.resize(out_len); -} -} // namespace - -Rsa::Rsa() : rsa_(nullptr) {} - -Rsa::Rsa(OSSL3_CONST RSA* ptr) : rsa_(ptr) {} - -const Rsa::PublicKey Rsa::getPublicKey() const { - if (rsa_ == nullptr) return {}; - PublicKey key; - RSA_get0_key(rsa_, &key.n, &key.e, &key.d); - return key; -} - -const Rsa::PrivateKey Rsa::getPrivateKey() const { - if (rsa_ == nullptr) return {}; - PrivateKey key; - RSA_get0_factors(rsa_, &key.p, &key.q); - RSA_get0_crt_params(rsa_, &key.dp, &key.dq, &key.qi); - return key; -} - -const std::optional Rsa::getPssParams() const { - if (rsa_ == nullptr) return std::nullopt; - const RSA_PSS_PARAMS* params = RSA_get0_pss_params(rsa_); - if (params == nullptr) return std::nullopt; - Rsa::PssParams ret{ - .digest = OBJ_nid2ln(NID_sha1), - .mgf1_digest = OBJ_nid2ln(NID_sha1), - .salt_length = 20, - }; - - if (params->hashAlgorithm != nullptr) { - const ASN1_OBJECT* hash_obj; - X509_ALGOR_get0(&hash_obj, nullptr, nullptr, params->hashAlgorithm); - ret.digest = OBJ_nid2ln(OBJ_obj2nid(hash_obj)); - } - - if (params->maskGenAlgorithm != nullptr) { - const ASN1_OBJECT* mgf_obj; - X509_ALGOR_get0(&mgf_obj, nullptr, nullptr, params->maskGenAlgorithm); - int mgf_nid = OBJ_obj2nid(mgf_obj); - if (mgf_nid == NID_mgf1) { - const ASN1_OBJECT* mgf1_hash_obj; - X509_ALGOR_get0(&mgf1_hash_obj, nullptr, nullptr, params->maskHash); - ret.mgf1_digest = OBJ_nid2ln(OBJ_obj2nid(mgf1_hash_obj)); - } - } - - if (params->saltLength != nullptr) { - if (ASN1_INTEGER_get_int64(&ret.salt_length, params->saltLength) != 1) { - return std::nullopt; - } - } - return ret; -} - -bool Rsa::setPublicKey(BignumPointer&& n, BignumPointer&& e) { - if (!n || !e) return false; - if (RSA_set0_key(const_cast(rsa_), n.get(), e.get(), nullptr) == 1) { - n.release(); - e.release(); - return true; - } - return false; -} - -bool Rsa::setPrivateKey(BignumPointer&& d, - BignumPointer&& q, - BignumPointer&& p, - BignumPointer&& dp, - BignumPointer&& dq, - BignumPointer&& qi) { - if (!RSA_set0_key(const_cast(rsa_), nullptr, nullptr, d.get())) { - return false; - } - d.release(); - - if (!RSA_set0_factors(const_cast(rsa_), p.get(), q.get())) { - return false; - } - p.release(); - q.release(); - - if (!RSA_set0_crt_params( - const_cast(rsa_), dp.get(), dq.get(), qi.get())) { - return false; - } - dp.release(); - dq.release(); - qi.release(); - return true; -} - -DataPointer Rsa::encrypt(const EVPKeyPointer& key, - const Rsa::CipherParams& params, - const Buffer in) { - if (!key) return {}; - return RSA_Cipher(key, params, in); -} - -DataPointer Rsa::decrypt(const EVPKeyPointer& key, - const Rsa::CipherParams& params, - const Buffer in) { - if (!key) return {}; - return RSA_Cipher(key, params, in); -} - -DataPointer Cipher::encrypt(const EVPKeyPointer& key, - const CipherParams& params, - const Buffer in) { - // public operation - return CipherImpl(key, params, in); -} - -DataPointer Cipher::decrypt(const EVPKeyPointer& key, - const CipherParams& params, - const Buffer in) { - // private operation - return CipherImpl(key, params, in); -} - -DataPointer Cipher::sign(const EVPKeyPointer& key, - const CipherParams& params, - const Buffer in) { - // private operation - return CipherImpl(key, params, in); -} - -DataPointer Cipher::recover(const EVPKeyPointer& key, - const CipherParams& params, - const Buffer in) { - // public operation - return CipherImpl( - key, params, in); -} - -namespace { -struct CipherCallbackContext { - Cipher::CipherNameCallback cb; - void operator()(const char* name) { cb(name); } -}; - -#if OPENSSL_VERSION_MAJOR >= 3 -template -void array_push_back(const TypeName* evp_ref, - const char* from, - const char* to, - void* arg) { - if (from == nullptr) return; - - const TypeName* real_instance = getbyname(from); - if (!real_instance) return; - - const char* real_name = getname(real_instance); - if (!real_name) return; - - // EVP_*_fetch() does not support alias names, so we need to pass it the - // real/original algorithm name. - // We use EVP_*_fetch() as a filter here because it will only return an - // instance if the algorithm is supported by the public OpenSSL APIs (some - // algorithms are used internally by OpenSSL and are also passed to this - // callback). - TypeName* fetched = fetch_type(nullptr, real_name, nullptr); - if (fetched == nullptr) return; - - free_type(fetched); - auto& cb = *(static_cast(arg)); - cb(from); -} -#else -template -void array_push_back(const TypeName* evp_ref, - const char* from, - const char* to, - void* arg) { - if (!from) return; - auto& cb = *(static_cast(arg)); - cb(from); -} -#endif -} // namespace - -void Cipher::ForEach(Cipher::CipherNameCallback callback) { - ClearErrorOnReturn clearErrorOnReturn; - CipherCallbackContext context; - context.cb = std::move(callback); - - EVP_CIPHER_do_all_sorted( -#if OPENSSL_VERSION_MAJOR >= 3 - array_push_back, -#else - array_push_back, -#endif - &context); -} - -// ============================================================================ - -Ec::Ec() : ec_(nullptr) {} - -Ec::Ec(OSSL3_CONST EC_KEY* key) : ec_(key) {} - -const EC_GROUP* Ec::getGroup() const { - return ECKeyPointer::GetGroup(ec_); -} - -int Ec::getCurve() const { - return EC_GROUP_get_curve_name(getGroup()); -} - -int Ec::GetCurveIdFromName(const char* name) { - int nid = EC_curve_nist2nid(name); - if (nid == NID_undef) { - nid = OBJ_sn2nid(name); - } - return nid; -} - -bool Ec::GetCurves(Ec::GetCurveCallback callback) { - const size_t count = EC_get_builtin_curves(nullptr, 0); - std::vector curves(count); - if (EC_get_builtin_curves(curves.data(), count) != count) { - return false; - } - for (auto curve : curves) { - if (!callback(OBJ_nid2sn(curve.nid))) return false; - } - return true; -} - -// ============================================================================ - -EVPMDCtxPointer::EVPMDCtxPointer() : ctx_(nullptr) {} - -EVPMDCtxPointer::EVPMDCtxPointer(EVP_MD_CTX* ctx) : ctx_(ctx) {} - -EVPMDCtxPointer::EVPMDCtxPointer(EVPMDCtxPointer&& other) noexcept - : ctx_(other.release()) {} - -EVPMDCtxPointer& EVPMDCtxPointer::operator=(EVPMDCtxPointer&& other) noexcept { - ctx_.reset(other.release()); - return *this; -} - -EVPMDCtxPointer::~EVPMDCtxPointer() { - reset(); -} - -void EVPMDCtxPointer::reset(EVP_MD_CTX* ctx) { - ctx_.reset(ctx); -} - -EVP_MD_CTX* EVPMDCtxPointer::release() { - return ctx_.release(); -} - -bool EVPMDCtxPointer::digestInit(const Digest& digest) { - if (!ctx_) return false; - return EVP_DigestInit_ex(ctx_.get(), digest, nullptr) > 0; -} - -bool EVPMDCtxPointer::digestUpdate(const Buffer& in) { - if (!ctx_) return false; - return EVP_DigestUpdate(ctx_.get(), in.data, in.len) > 0; -} - -DataPointer EVPMDCtxPointer::digestFinal(size_t length) { - if (!ctx_) return {}; - - auto buf = DataPointer::Alloc(length); - if (!buf) return {}; - - Buffer buffer = buf; - - if (!digestFinalInto(&buffer)) [[unlikely]] { - return {}; - } - - return buf; -} - -bool EVPMDCtxPointer::digestFinalInto(Buffer* buf) { - if (!ctx_) return false; - - auto ptr = static_cast(buf->data); - - int ret = (buf->len == getExpectedSize()) - ? EVP_DigestFinal_ex(ctx_.get(), ptr, nullptr) - : EVP_DigestFinalXOF(ctx_.get(), ptr, buf->len); - - if (ret != 1) [[unlikely]] - return false; - - return true; -} - -size_t EVPMDCtxPointer::getExpectedSize() { - if (!ctx_) return 0; - return EVP_MD_CTX_size(ctx_.get()); -} - -size_t EVPMDCtxPointer::getDigestSize() const { - return EVP_MD_size(getDigest()); -} - -const EVP_MD* EVPMDCtxPointer::getDigest() const { - if (!ctx_) return nullptr; - return EVP_MD_CTX_md(ctx_.get()); -} - -bool EVPMDCtxPointer::hasXofFlag() const { - if (!ctx_) return false; - return (EVP_MD_flags(getDigest()) & EVP_MD_FLAG_XOF) == EVP_MD_FLAG_XOF; -} - -bool EVPMDCtxPointer::copyTo(const EVPMDCtxPointer& other) const { - if (!ctx_ || !other) return {}; - if (EVP_MD_CTX_copy(other.get(), ctx_.get()) != 1) return false; - return true; -} - -std::optional EVPMDCtxPointer::signInit(const EVPKeyPointer& key, - const Digest& digest) { - EVP_PKEY_CTX* ctx = nullptr; - if (!EVP_DigestSignInit(ctx_.get(), &ctx, digest, nullptr, key.get())) { - return std::nullopt; - } - return ctx; -} - -std::optional EVPMDCtxPointer::verifyInit( - const EVPKeyPointer& key, const Digest& digest) { - EVP_PKEY_CTX* ctx = nullptr; - if (!EVP_DigestVerifyInit(ctx_.get(), &ctx, digest, nullptr, key.get())) { - return std::nullopt; - } - return ctx; -} - -DataPointer EVPMDCtxPointer::signOneShot( - const Buffer& buf) const { - if (!ctx_) return {}; - size_t len; - if (!EVP_DigestSign(ctx_.get(), nullptr, &len, buf.data, buf.len)) { - return {}; - } - auto data = DataPointer::Alloc(len); - if (!data) [[unlikely]] - return {}; - - if (!EVP_DigestSign(ctx_.get(), - static_cast(data.get()), - &len, - buf.data, - buf.len)) { - return {}; - } - return data; -} - -DataPointer EVPMDCtxPointer::sign( - const Buffer& buf) const { - if (!ctx_) [[unlikely]] - return {}; - size_t len; - if (!EVP_DigestSignUpdate(ctx_.get(), buf.data, buf.len) || - !EVP_DigestSignFinal(ctx_.get(), nullptr, &len)) { - return {}; - } - auto data = DataPointer::Alloc(len); - if (!data) [[unlikely]] - return {}; - if (!EVP_DigestSignFinal( - ctx_.get(), static_cast(data.get()), &len)) { - return {}; - } - return data.resize(len); -} - -bool EVPMDCtxPointer::verify(const Buffer& buf, - const Buffer& sig) const { - if (!ctx_) return false; - int ret = EVP_DigestVerify(ctx_.get(), sig.data, sig.len, buf.data, buf.len); - return ret == 1; -} - -EVPMDCtxPointer EVPMDCtxPointer::New() { - return EVPMDCtxPointer(EVP_MD_CTX_new()); -} - -// ============================================================================ - -bool extractP1363(const Buffer& buf, - unsigned char* dest, - size_t n) { - auto asn1_sig = ECDSASigPointer::Parse(buf); - if (!asn1_sig) return false; - - return BignumPointer::EncodePaddedInto(asn1_sig.r(), dest, n) > 0 && - BignumPointer::EncodePaddedInto(asn1_sig.s(), dest + n, n) > 0; -} - -// ============================================================================ - -HMACCtxPointer::HMACCtxPointer() : ctx_(nullptr) {} - -HMACCtxPointer::HMACCtxPointer(HMAC_CTX* ctx) : ctx_(ctx) {} - -HMACCtxPointer::HMACCtxPointer(HMACCtxPointer&& other) noexcept - : ctx_(other.release()) {} - -HMACCtxPointer& HMACCtxPointer::operator=(HMACCtxPointer&& other) noexcept { - ctx_.reset(other.release()); - return *this; -} - -HMACCtxPointer::~HMACCtxPointer() { - reset(); -} - -void HMACCtxPointer::reset(HMAC_CTX* ctx) { - ctx_.reset(ctx); -} - -HMAC_CTX* HMACCtxPointer::release() { - return ctx_.release(); -} - -bool HMACCtxPointer::init(const Buffer& buf, const Digest& md) { - if (!ctx_) return false; - const EVP_MD* md_ptr = md; - return HMAC_Init_ex(ctx_.get(), buf.data, buf.len, md_ptr, nullptr) == 1; -} - -bool HMACCtxPointer::update(const Buffer& buf) { - if (!ctx_) return false; - return HMAC_Update(ctx_.get(), - static_cast(buf.data), - buf.len) == 1; -} - -DataPointer HMACCtxPointer::digest() { - auto data = DataPointer::Alloc(EVP_MAX_MD_SIZE); - if (!data) return {}; - Buffer buf = data; - if (!digestInto(&buf)) return {}; - return data.resize(buf.len); -} - -bool HMACCtxPointer::digestInto(Buffer* buf) { - if (!ctx_) return false; - - unsigned int len = buf->len; - if (!HMAC_Final(ctx_.get(), static_cast(buf->data), &len)) { - return false; - } - buf->len = len; - return true; -} - -HMACCtxPointer HMACCtxPointer::New() { - return HMACCtxPointer(HMAC_CTX_new()); -} - -DataPointer hashDigest(const Buffer& buf, - const EVP_MD* md) { - if (md == nullptr) return {}; - size_t md_len = EVP_MD_size(md); - unsigned int result_size; - auto data = DataPointer::Alloc(md_len); - if (!data) return {}; - - if (!EVP_Digest(buf.data, - buf.len, - reinterpret_cast(data.get()), - &result_size, - md, - nullptr)) { - return {}; - } - - return data.resize(result_size); -} - -DataPointer xofHashDigest(const Buffer& buf, - const EVP_MD* md, - size_t output_length) { - if (md == nullptr) return {}; - - EVPMDCtxPointer ctx = EVPMDCtxPointer::New(); - if (!ctx) return {}; - if (ctx.digestInit(md) != 1) { - return {}; - } - if (ctx.digestUpdate(reinterpret_cast&>(buf)) != 1) { - return {}; - } - return ctx.digestFinal(output_length); -} - -// ============================================================================ - -X509Name::X509Name() : name_(nullptr), total_(0) {} - -X509Name::X509Name(const X509_NAME* name) - : name_(name), total_(X509_NAME_entry_count(name)) {} - -X509Name::Iterator::Iterator(const X509Name& name, int pos) - : name_(name), loc_(pos) {} - -X509Name::Iterator& X509Name::Iterator::operator++() { - ++loc_; - return *this; -} - -X509Name::Iterator::operator bool() const { - return loc_ < name_.total_; -} - -bool X509Name::Iterator::operator==(const Iterator& other) const { - return loc_ == other.loc_; -} - -bool X509Name::Iterator::operator!=(const Iterator& other) const { - return loc_ != other.loc_; -} - -std::pair X509Name::Iterator::operator*() const { - if (loc_ == name_.total_) return {{}, {}}; - - X509_NAME_ENTRY* entry = X509_NAME_get_entry(name_, loc_); - if (entry == nullptr) [[unlikely]] - return {{}, {}}; - - ASN1_OBJECT* name = X509_NAME_ENTRY_get_object(entry); - ASN1_STRING* value = X509_NAME_ENTRY_get_data(entry); - - if (name == nullptr || value == nullptr) [[unlikely]] { - return {{}, {}}; - } - - int nid = OBJ_obj2nid(name); - std::string name_str; - if (nid != NID_undef) { - name_str = std::string(OBJ_nid2sn(nid)); - } else { - char buf[80]; - OBJ_obj2txt(buf, sizeof(buf), name, 0); - name_str = std::string(buf); - } - - unsigned char* value_str; - int value_str_size = ASN1_STRING_to_UTF8(&value_str, value); - - return { - std::move(name_str), - std::string(reinterpret_cast(value_str), value_str_size)}; -} - -// ============================================================================ - -Dsa::Dsa() : dsa_(nullptr) {} - -Dsa::Dsa(OSSL3_CONST DSA* dsa) : dsa_(dsa) {} - -const BIGNUM* Dsa::getP() const { - if (dsa_ == nullptr) return nullptr; - const BIGNUM* p; - DSA_get0_pqg(dsa_, &p, nullptr, nullptr); - return p; -} - -const BIGNUM* Dsa::getQ() const { - if (dsa_ == nullptr) return nullptr; - const BIGNUM* q; - DSA_get0_pqg(dsa_, nullptr, &q, nullptr); - return q; -} - -size_t Dsa::getModulusLength() const { - if (dsa_ == nullptr) return 0; - return BignumPointer::GetBitCount(getP()); -} - -size_t Dsa::getDivisorLength() const { - if (dsa_ == nullptr) return 0; - return BignumPointer::GetBitCount(getQ()); -} - -// ============================================================================ - -size_t Digest::size() const { - if (md_ == nullptr) return 0; - return EVP_MD_size(md_); -} - -const Digest Digest::MD5 = Digest(EVP_md5()); -const Digest Digest::SHA1 = Digest(EVP_sha1()); -const Digest Digest::SHA256 = Digest(EVP_sha256()); -const Digest Digest::SHA384 = Digest(EVP_sha384()); -const Digest Digest::SHA512 = Digest(EVP_sha512()); - -const Digest Digest::FromName(const char* name) { - return ncrypto::getDigestByName(name); -} - -// ============================================================================ -// KEM Implementation -#if OPENSSL_VERSION_MAJOR >= 3 -#if !OPENSSL_VERSION_PREREQ(3, 5) -bool KEM::SetOperationParameter(EVP_PKEY_CTX* ctx, const EVPKeyPointer& key) { - const char* operation = nullptr; - - switch (EVP_PKEY_id(key.get())) { - case EVP_PKEY_RSA: - operation = OSSL_KEM_PARAM_OPERATION_RSASVE; - break; -#if OPENSSL_VERSION_PREREQ(3, 2) - case EVP_PKEY_EC: - case EVP_PKEY_X25519: - case EVP_PKEY_X448: - operation = OSSL_KEM_PARAM_OPERATION_DHKEM; - break; -#endif - default: - unreachable(); - } - - if (operation != nullptr) { - OSSL_PARAM params[] = { - OSSL_PARAM_utf8_string( - OSSL_KEM_PARAM_OPERATION, const_cast(operation), 0), - OSSL_PARAM_END}; - - if (EVP_PKEY_CTX_set_params(ctx, params) <= 0) { - return false; - } - } - - return true; -} -#endif - -std::optional KEM::Encapsulate( - const EVPKeyPointer& public_key) { - ClearErrorOnReturn clear_error_on_return; - - auto ctx = public_key.newCtx(); - if (!ctx) return std::nullopt; - - if (EVP_PKEY_encapsulate_init(ctx.get(), nullptr) <= 0) { - return std::nullopt; - } - -#if !OPENSSL_VERSION_PREREQ(3, 5) - if (!SetOperationParameter(ctx.get(), public_key)) { - return std::nullopt; - } -#endif - - // Determine output buffer sizes - size_t ciphertext_len = 0; - size_t shared_key_len = 0; - - if (EVP_PKEY_encapsulate( - ctx.get(), nullptr, &ciphertext_len, nullptr, &shared_key_len) <= 0) { - return std::nullopt; - } - - auto ciphertext = DataPointer::Alloc(ciphertext_len); - auto shared_key = DataPointer::Alloc(shared_key_len); - if (!ciphertext || !shared_key) return std::nullopt; - - if (EVP_PKEY_encapsulate(ctx.get(), - static_cast(ciphertext.get()), - &ciphertext_len, - static_cast(shared_key.get()), - &shared_key_len) <= 0) { - return std::nullopt; - } - - return EncapsulateResult(std::move(ciphertext), std::move(shared_key)); -} - -DataPointer KEM::Decapsulate(const EVPKeyPointer& private_key, - const Buffer& ciphertext) { - ClearErrorOnReturn clear_error_on_return; - - auto ctx = private_key.newCtx(); - if (!ctx) return {}; - - if (EVP_PKEY_decapsulate_init(ctx.get(), nullptr) <= 0) { - return {}; - } - -#if !OPENSSL_VERSION_PREREQ(3, 5) - if (!SetOperationParameter(ctx.get(), private_key)) { - return {}; - } -#endif - - // First pass: determine shared secret size - size_t shared_key_len = 0; - if (EVP_PKEY_decapsulate(ctx.get(), - nullptr, - &shared_key_len, - static_cast(ciphertext.data), - ciphertext.len) <= 0) { - return {}; - } - - auto shared_key = DataPointer::Alloc(shared_key_len); - if (!shared_key) return {}; - - if (EVP_PKEY_decapsulate(ctx.get(), - static_cast(shared_key.get()), - &shared_key_len, - static_cast(ciphertext.data), - ciphertext.len) <= 0) { - return {}; - } - - return shared_key; -} - -#endif // OPENSSL_VERSION_MAJOR >= 3 - -} // namespace ncrypto diff --git a/packages/react-native-quick-crypto/deps/ncrypto/ncrypto.h b/packages/react-native-quick-crypto/deps/ncrypto/ncrypto.h deleted file mode 100644 index 6c62aa28..00000000 --- a/packages/react-native-quick-crypto/deps/ncrypto/ncrypto.h +++ /dev/null @@ -1,1625 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifndef OPENSSL_NO_ENGINE -#include -#endif // !OPENSSL_NO_ENGINE -// The FIPS-related functions are only available -// when the OpenSSL itself was compiled with FIPS support. -#if defined(OPENSSL_FIPS) && OPENSSL_VERSION_MAJOR < 3 -#include -#endif // OPENSSL_FIPS - -// Define OPENSSL_WITH_PQC for post-quantum cryptography support -#if OPENSSL_VERSION_NUMBER >= 0x30500000L -#define OPENSSL_WITH_PQC 1 -#define EVP_PKEY_ML_KEM_512 NID_ML_KEM_512 -#define EVP_PKEY_ML_KEM_768 NID_ML_KEM_768 -#define EVP_PKEY_ML_KEM_1024 NID_ML_KEM_1024 -#include -#endif - -#if OPENSSL_VERSION_MAJOR >= 3 -#define OSSL3_CONST const -#else -#define OSSL3_CONST -#endif - -#ifdef __GNUC__ -#define NCRYPTO_MUST_USE_RESULT __attribute__((warn_unused_result)) -#else -#define NCRYPTO_MUST_USE_RESULT -#endif - -#ifdef OPENSSL_IS_BORINGSSL -// Boringssl has opted to use size_t for some size related -// APIs while Openssl is still using ints -using OPENSSL_SIZE_T = size_t; -#else -using OPENSSL_SIZE_T = int; -#endif - -namespace ncrypto { - -// ============================================================================ -// Utility macros - -#if NCRYPTO_DEVELOPMENT_CHECKS -#define NCRYPTO_STR(x) #x -#define NCRYPTO_REQUIRE(EXPR) \ - { \ - if (!(EXPR) { abort(); }) } - -#define NCRYPTO_FAIL(MESSAGE) \ - do { \ - std::cerr << "FAIL: " << (MESSAGE) << std::endl; \ - abort(); \ - } while (0); -#define NCRYPTO_ASSERT_EQUAL(LHS, RHS, MESSAGE) \ - do { \ - if (LHS != RHS) { \ - std::cerr << "Mismatch: '" << LHS << "' - '" << RHS << "'" << std::endl; \ - NCRYPTO_FAIL(MESSAGE); \ - } \ - } while (0); -#define NCRYPTO_ASSERT_TRUE(COND) \ - do { \ - if (!(COND)) { \ - std::cerr << "Assert at line " << __LINE__ << " of file " << __FILE__ \ - << std::endl; \ - NCRYPTO_FAIL(NCRYPTO_STR(COND)); \ - } \ - } while (0); -#else -#define NCRYPTO_FAIL(MESSAGE) -#define NCRYPTO_ASSERT_EQUAL(LHS, RHS, MESSAGE) -#define NCRYPTO_ASSERT_TRUE(COND) -#endif - -#define NCRYPTO_DISALLOW_COPY(Name) \ - Name(const Name&) = delete; \ - Name& operator=(const Name&) = delete; -#define NCRYPTO_DISALLOW_MOVE(Name) \ - Name(Name&&) = delete; \ - Name& operator=(Name&&) = delete; -#define NCRYPTO_DISALLOW_COPY_AND_MOVE(Name) \ - NCRYPTO_DISALLOW_COPY(Name) \ - NCRYPTO_DISALLOW_MOVE(Name) -#define NCRYPTO_DISALLOW_NEW_DELETE() \ - void* operator new(size_t) = delete; \ - void operator delete(void*) = delete; - -[[noreturn]] inline void unreachable() { -#ifdef __GNUC__ - __builtin_unreachable(); -#elif defined(_MSC_VER) - __assume(false); -#else -#endif -} - -static constexpr int kX509NameFlagsMultiline = - ASN1_STRFLGS_ESC_2253 | ASN1_STRFLGS_ESC_CTRL | ASN1_STRFLGS_UTF8_CONVERT | - XN_FLAG_SEP_MULTILINE | XN_FLAG_FN_SN; - -// ============================================================================ -// Error handling utilities - -// Capture the current OpenSSL Error Stack. The stack will be ordered such -// that the error currently at the top of the stack is at the end of the -// list and the error at the bottom of the stack is at the beginning. -class CryptoErrorList final { - public: - enum class Option { NONE, CAPTURE_ON_CONSTRUCT }; - CryptoErrorList(Option option = Option::CAPTURE_ON_CONSTRUCT); - - void capture(); - - // Add an error message to the end of the stack. - void add(std::string message); - - inline const std::string& peek_back() const { return errors_.back(); } - inline size_t size() const { return errors_.size(); } - inline bool empty() const { return errors_.empty(); } - - inline auto begin() const noexcept { return errors_.begin(); } - inline auto end() const noexcept { return errors_.end(); } - inline auto rbegin() const noexcept { return errors_.rbegin(); } - inline auto rend() const noexcept { return errors_.rend(); } - - std::optional pop_back(); - std::optional pop_front(); - - private: - std::list errors_; -}; - -// Forcibly clears the error stack on destruction. This stops stale errors -// from popping up later in the lifecycle of crypto operations where they -// would cause spurious failures. It is a rather blunt method, though, and -// ERR_clear_error() isn't necessarily cheap. -// -// If created with a pointer to a CryptoErrorList, the current OpenSSL error -// stack will be captured before clearing the error. -class ClearErrorOnReturn final { - public: - ClearErrorOnReturn(CryptoErrorList* errors = nullptr); - ~ClearErrorOnReturn(); - NCRYPTO_DISALLOW_COPY_AND_MOVE(ClearErrorOnReturn) - NCRYPTO_DISALLOW_NEW_DELETE() - - int peekError(); - - private: - CryptoErrorList* errors_; -}; - -// Pop errors from OpenSSL's error stack that were added between when this -// was constructed and destructed. -// -// If created with a pointer to a CryptoErrorList, the current OpenSSL error -// stack will be captured before resetting the error to the mark. -class MarkPopErrorOnReturn final { - public: - MarkPopErrorOnReturn(CryptoErrorList* errors = nullptr); - ~MarkPopErrorOnReturn(); - NCRYPTO_DISALLOW_COPY_AND_MOVE(MarkPopErrorOnReturn) - NCRYPTO_DISALLOW_NEW_DELETE() - - int peekError(); - - private: - CryptoErrorList* errors_; -}; - -// TODO(@jasnell): Eventually replace with std::expected when we are able to -// bump up to c++23. -template -struct Result final { - const bool has_value; - T value; - std::optional error = std::nullopt; - std::optional openssl_error = std::nullopt; - Result(T&& value) : has_value(true), value(std::move(value)) {} - Result(E&& error, std::optional openssl_error = std::nullopt) - : has_value(false), - error(std::move(error)), - openssl_error(std::move(openssl_error)) {} - inline operator bool() const { return has_value; } -}; - -// ============================================================================ -// Various smart pointer aliases for OpenSSL types. - -template -struct FunctionDeleter { - void operator()(T* pointer) const { function(pointer); } - typedef std::unique_ptr Pointer; -}; - -template -using DeleteFnPtr = typename FunctionDeleter::Pointer; - -using PKCS8Pointer = DeleteFnPtr; -using RSAPointer = DeleteFnPtr; -using SSLSessionPointer = DeleteFnPtr; - -class BIOPointer; -class BignumPointer; -class CipherCtxPointer; -class DataPointer; -class DHPointer; -class ECKeyPointer; -class EVPKeyPointer; -class EVPMDCtxPointer; -class SSLCtxPointer; -class SSLPointer; -class X509View; -class X509Pointer; -class ECDSASigPointer; -class ECGroupPointer; -class ECPointPointer; -class ECKeyPointer; -class Dsa; -class Rsa; -class Ec; - -struct StackOfXASN1Deleter { - void operator()(STACK_OF(ASN1_OBJECT) * p) const { - sk_ASN1_OBJECT_pop_free(p, ASN1_OBJECT_free); - } -}; -using StackOfASN1 = std::unique_ptr; - -// An unowned, unmanaged pointer to a buffer of data. -template -struct Buffer { - T* data = nullptr; - size_t len = 0; -}; - -class Digest final { - public: - static constexpr size_t MAX_SIZE = EVP_MAX_MD_SIZE; - Digest() = default; - Digest(const EVP_MD* md) : md_(md) {} - Digest(const Digest&) = default; - Digest& operator=(const Digest&) = default; - inline Digest& operator=(const EVP_MD* md) { - md_ = md; - return *this; - } - NCRYPTO_DISALLOW_MOVE(Digest) - - size_t size() const; - - inline const EVP_MD* get() const { return md_; } - inline operator const EVP_MD*() const { return md_; } - inline operator bool() const { return md_ != nullptr; } - - static const Digest MD5; - static const Digest SHA1; - static const Digest SHA256; - static const Digest SHA384; - static const Digest SHA512; - - static const Digest FromName(const char* name); - - private: - const EVP_MD* md_ = nullptr; -}; - -// Computes a fixed-length digest. -DataPointer hashDigest(const Buffer& data, - const EVP_MD* md); -// Computes a variable-length digest for XOF algorithms (e.g. SHAKE128). -DataPointer xofHashDigest(const Buffer& data, - const EVP_MD* md, - size_t length); - -class Cipher final { - public: - static constexpr size_t MAX_KEY_LENGTH = EVP_MAX_KEY_LENGTH; - static constexpr size_t MAX_IV_LENGTH = EVP_MAX_IV_LENGTH; -#ifdef EVP_MAX_AEAD_TAG_LENGTH - static constexpr size_t MAX_AUTH_TAG_LENGTH = EVP_MAX_AEAD_TAG_LENGTH; -#else - static constexpr size_t MAX_AUTH_TAG_LENGTH = 16; -#endif - static_assert(EVP_GCM_TLS_TAG_LEN <= MAX_AUTH_TAG_LENGTH && - EVP_CCM_TLS_TAG_LEN <= MAX_AUTH_TAG_LENGTH && - EVP_CHACHAPOLY_TLS_TAG_LEN <= MAX_AUTH_TAG_LENGTH); - - Cipher() = default; - Cipher(const EVP_CIPHER* cipher) : cipher_(cipher) {} - Cipher(const Cipher&) = default; - Cipher& operator=(const Cipher&) = default; - inline Cipher& operator=(const EVP_CIPHER* cipher) { - cipher_ = cipher; - return *this; - } - NCRYPTO_DISALLOW_MOVE(Cipher) - - inline const EVP_CIPHER* get() const { return cipher_; } - inline operator const EVP_CIPHER*() const { return cipher_; } - inline operator bool() const { return cipher_ != nullptr; } - - int getNid() const; - int getMode() const; - int getIvLength() const; - int getKeyLength() const; - int getBlockSize() const; - std::string_view getModeLabel() const; - const char* getName() const; - - bool isGcmMode() const; - bool isWrapMode() const; - bool isCtrMode() const; - bool isCcmMode() const; - bool isOcbMode() const; - bool isStreamMode() const; - bool isChaCha20Poly1305() const; - - bool isSupportedAuthenticatedMode() const; - - int bytesToKey(const Digest& digest, - const Buffer& input, - unsigned char* key, - unsigned char* iv) const; - - static const Cipher FromName(const char* name); - static const Cipher FromNid(int nid); - static const Cipher FromCtx(const CipherCtxPointer& ctx); - - using CipherNameCallback = std::function; - - // Iterates the known ciphers if the underlying implementation - // is able to do so. - static void ForEach(CipherNameCallback callback); - - // Utilities to get various ciphers by type. If the underlying - // implementation does not support the requested cipher, then - // the result will be an empty Cipher object whose bool operator - // will return false. - - static const Cipher EMPTY; - static const Cipher AES_128_CBC; - static const Cipher AES_192_CBC; - static const Cipher AES_256_CBC; - static const Cipher AES_128_CTR; - static const Cipher AES_192_CTR; - static const Cipher AES_256_CTR; - static const Cipher AES_128_GCM; - static const Cipher AES_192_GCM; - static const Cipher AES_256_GCM; - static const Cipher AES_128_KW; - static const Cipher AES_192_KW; - static const Cipher AES_256_KW; - static const Cipher AES_128_OCB; - static const Cipher AES_192_OCB; - static const Cipher AES_256_OCB; - static const Cipher CHACHA20_POLY1305; - - struct CipherParams { - int padding; - Digest digest; - const Buffer label; - }; - - static DataPointer encrypt(const EVPKeyPointer& key, - const CipherParams& params, - const Buffer in); - static DataPointer decrypt(const EVPKeyPointer& key, - const CipherParams& params, - const Buffer in); - - static DataPointer sign(const EVPKeyPointer& key, - const CipherParams& params, - const Buffer in); - - static DataPointer recover(const EVPKeyPointer& key, - const CipherParams& params, - const Buffer in); - - static constexpr bool IsValidGCMTagLength(unsigned int tag_len) { - return tag_len == 4 || tag_len == 8 || (tag_len >= 12 && tag_len <= 16); - } - - private: - const EVP_CIPHER* cipher_ = nullptr; -}; - -// ============================================================================ -// DSA - -class Dsa final { - public: - Dsa(); - Dsa(OSSL3_CONST DSA* dsa); - NCRYPTO_DISALLOW_COPY_AND_MOVE(Dsa) - - inline operator bool() const { return dsa_ != nullptr; } - inline operator OSSL3_CONST DSA*() const { return dsa_; } - - const BIGNUM* getP() const; - const BIGNUM* getQ() const; - size_t getModulusLength() const; - size_t getDivisorLength() const; - - private: - OSSL3_CONST DSA* dsa_; -}; - -// ============================================================================ -// RSA - -class Rsa final { - public: - Rsa(); - Rsa(OSSL3_CONST RSA* rsa); - NCRYPTO_DISALLOW_COPY_AND_MOVE(Rsa) - - inline operator bool() const { return rsa_ != nullptr; } - inline operator OSSL3_CONST RSA*() const { return rsa_; } - - struct PublicKey { - const BIGNUM* n; - const BIGNUM* e; - const BIGNUM* d; - }; - struct PrivateKey { - const BIGNUM* p; - const BIGNUM* q; - const BIGNUM* dp; - const BIGNUM* dq; - const BIGNUM* qi; - }; - struct PssParams { - std::string_view digest = "sha1"; - std::optional mgf1_digest = "sha1"; - int64_t salt_length = 20; - }; - - const PublicKey getPublicKey() const; - const PrivateKey getPrivateKey() const; - const std::optional getPssParams() const; - - bool setPublicKey(BignumPointer&& n, BignumPointer&& e); - bool setPrivateKey(BignumPointer&& d, - BignumPointer&& q, - BignumPointer&& p, - BignumPointer&& dp, - BignumPointer&& dq, - BignumPointer&& qi); - - using CipherParams = Cipher::CipherParams; - - static DataPointer encrypt(const EVPKeyPointer& key, - const CipherParams& params, - const Buffer in); - static DataPointer decrypt(const EVPKeyPointer& key, - const CipherParams& params, - const Buffer in); - - private: - OSSL3_CONST RSA* rsa_; -}; - -class Ec final { - public: - Ec(); - Ec(OSSL3_CONST EC_KEY* key); - NCRYPTO_DISALLOW_COPY_AND_MOVE(Ec) - - const EC_GROUP* getGroup() const; - int getCurve() const; - - inline operator bool() const { return ec_ != nullptr; } - inline operator OSSL3_CONST EC_KEY*() const { return ec_; } - - static int GetCurveIdFromName(const char* name); - - using GetCurveCallback = std::function; - static bool GetCurves(GetCurveCallback callback); - - private: - OSSL3_CONST EC_KEY* ec_ = nullptr; -}; - -// A managed pointer to a buffer of data. When destroyed the underlying -// buffer will be freed. -class DataPointer final { - public: - static DataPointer Alloc(size_t len); - static DataPointer Copy(const Buffer& buffer); - - // Attempts to allocate the buffer space using the secure heap, if - // supported/enabled. If the secure heap is disabled, then this - // ends up being equivalent to Alloc(len). Note that allocation - // will fail if there is not enough free space remaining in the - // secure heap space. - static DataPointer SecureAlloc(size_t len); - - // If the secure heap is enabled, returns the amount of data that - // has been allocated from the heap. - static size_t GetSecureHeapUsed(); - - enum class InitSecureHeapResult { - FAILED, - UNABLE_TO_MEMORY_MAP, - OK, - }; - - // Attempt to initialize the secure heap. The secure heap is not - // supported on all operating systems and whenever boringssl is - // used. - static InitSecureHeapResult TryInitSecureHeap(size_t amount, size_t min); - - DataPointer() = default; - explicit DataPointer(void* data, size_t len, bool secure = false); - explicit DataPointer(const Buffer& buffer, bool secure = false); - DataPointer(DataPointer&& other) noexcept; - DataPointer& operator=(DataPointer&& other) noexcept; - NCRYPTO_DISALLOW_COPY(DataPointer) - ~DataPointer(); - - inline bool operator==(std::nullptr_t) noexcept { return data_ == nullptr; } - inline operator bool() const { return data_ != nullptr; } - - template - inline T* get() const noexcept { - return static_cast(data_); - } - - inline size_t size() const noexcept { return len_; } - void reset(void* data = nullptr, size_t len = 0); - void reset(const Buffer& buffer); - - // Sets the underlying data buffer to all zeros. - void zero(); - - DataPointer resize(size_t len); - - // Releases ownership of the underlying data buffer. It is the caller's - // responsibility to ensure the buffer is appropriately freed. - Buffer release(); - - // Returns a Buffer struct that is a view of the underlying data. - template - inline operator const Buffer() const { - return { - .data = static_cast(data_), - .len = len_, - }; - } - - bool isSecure() const { return secure_; } - - private: - void* data_ = nullptr; - size_t len_ = 0; - bool secure_ = false; -}; - -class BIOPointer final { - public: - static BIOPointer NewMem(); - static BIOPointer NewSecMem(); - static BIOPointer New(const BIO_METHOD* method); - static BIOPointer New(const void* data, size_t len); - static BIOPointer New(const BIGNUM* bn); - static BIOPointer NewFile(const char* filename, const char* mode); - static BIOPointer NewFp(FILE* fd, int flags); - - template - static BIOPointer New(const Buffer& buf) { - return New(buf.data, buf.len); - } - - BIOPointer() = default; - BIOPointer(std::nullptr_t) : bio_(nullptr) {} - explicit BIOPointer(BIO* bio); - BIOPointer(BIOPointer&& other) noexcept; - BIOPointer& operator=(BIOPointer&& other) noexcept; - NCRYPTO_DISALLOW_COPY(BIOPointer) - ~BIOPointer(); - - inline bool operator==(std::nullptr_t) noexcept { return bio_ == nullptr; } - inline operator bool() const { return bio_ != nullptr; } - inline BIO* get() const noexcept { return bio_.get(); } - - inline operator BUF_MEM*() const { - BUF_MEM* mem = nullptr; - if (!bio_) return mem; - BIO_get_mem_ptr(bio_.get(), &mem); - return mem; - } - - inline operator BIO*() const { return bio_.get(); } - - void reset(BIO* bio = nullptr); - BIO* release(); - - bool resetBio() const; - - static int Write(BIOPointer* bio, std::string_view message); - - template - static void Printf(BIOPointer* bio, const char* format, Args... args) { - if (bio == nullptr || !*bio) return; - BIO_printf(bio->get(), format, std::forward(args...)); - } - - private: - mutable DeleteFnPtr bio_; -}; - -class BignumPointer final { - public: - BignumPointer() = default; - explicit BignumPointer(BIGNUM* bignum); - explicit BignumPointer(const unsigned char* data, size_t len); - BignumPointer(BignumPointer&& other) noexcept; - BignumPointer& operator=(BignumPointer&& other) noexcept; - NCRYPTO_DISALLOW_COPY(BignumPointer) - ~BignumPointer(); - - int operator<=>(const BignumPointer& other) const noexcept; - int operator<=>(const BIGNUM* other) const noexcept; - inline operator bool() const { return bn_ != nullptr; } - inline BIGNUM* get() const noexcept { return bn_.get(); } - void reset(BIGNUM* bn = nullptr); - void reset(const unsigned char* data, size_t len); - BIGNUM* release(); - - bool isZero() const; - bool isOne() const; - - bool setWord(unsigned long w); // NOLINT(runtime/int) - unsigned long getWord() const; // NOLINT(runtime/int) - - size_t byteLength() const; - - DataPointer toHex() const; - DataPointer encode() const; - DataPointer encodePadded(size_t size) const; - size_t encodeInto(unsigned char* out) const; - size_t encodePaddedInto(unsigned char* out, size_t size) const; - - using PrimeCheckCallback = std::function; - int isPrime(int checks, - PrimeCheckCallback cb = defaultPrimeCheckCallback) const; - struct PrimeConfig { - int bits; - bool safe = false; - const BignumPointer& add; - const BignumPointer& rem; - }; - - static BignumPointer NewPrime( - const PrimeConfig& params, - PrimeCheckCallback cb = defaultPrimeCheckCallback); - - bool generate(const PrimeConfig& params, - PrimeCheckCallback cb = defaultPrimeCheckCallback) const; - - static BignumPointer New(); - static BignumPointer NewSecure(); - static BignumPointer NewSub(const BignumPointer& a, const BignumPointer& b); - static BignumPointer NewLShift(size_t length); - - static DataPointer Encode(const BIGNUM* bn); - static DataPointer EncodePadded(const BIGNUM* bn, size_t size); - static size_t EncodePaddedInto(const BIGNUM* bn, - unsigned char* out, - size_t size); - static int GetBitCount(const BIGNUM* bn); - static int GetByteCount(const BIGNUM* bn); - static unsigned long GetWord(const BIGNUM* bn); // NOLINT(runtime/int) - static const BIGNUM* One(); - - BignumPointer clone(); - - private: - DeleteFnPtr bn_; - - static bool defaultPrimeCheckCallback(int, int) { return 1; } -}; - -class CipherCtxPointer final { - public: - static CipherCtxPointer New(); - - CipherCtxPointer() = default; - explicit CipherCtxPointer(EVP_CIPHER_CTX* ctx); - CipherCtxPointer(CipherCtxPointer&& other) noexcept; - CipherCtxPointer& operator=(CipherCtxPointer&& other) noexcept; - NCRYPTO_DISALLOW_COPY(CipherCtxPointer) - ~CipherCtxPointer(); - - inline bool operator==(std::nullptr_t) const noexcept { - return ctx_ == nullptr; - } - inline operator bool() const { return ctx_ != nullptr; } - inline EVP_CIPHER_CTX* get() const { return ctx_.get(); } - inline operator EVP_CIPHER_CTX*() const { return ctx_.get(); } - void reset(EVP_CIPHER_CTX* ctx = nullptr); - EVP_CIPHER_CTX* release(); - - void setAllowWrap(); - - bool setKeyLength(size_t length); - bool setIvLength(size_t length); - bool setAeadTag(const Buffer& tag); - bool setAeadTagLength(size_t length); - bool setPadding(bool padding); - bool init(const Cipher& cipher, - bool encrypt, - const unsigned char* key = nullptr, - const unsigned char* iv = nullptr); - - int getBlockSize() const; - int getMode() const; - int getNid() const; - - bool isGcmMode() const; - bool isOcbMode() const; - bool isCcmMode() const; - bool isWrapMode() const; - bool isChaCha20Poly1305() const; - - bool update(const Buffer& in, - unsigned char* out, - int* out_len, - bool finalize = false); - bool getAeadTag(size_t len, unsigned char* out); - - private: - DeleteFnPtr ctx_; -}; - -class EVPKeyCtxPointer final { - public: - EVPKeyCtxPointer(); - explicit EVPKeyCtxPointer(EVP_PKEY_CTX* ctx); - EVPKeyCtxPointer(EVPKeyCtxPointer&& other) noexcept; - EVPKeyCtxPointer& operator=(EVPKeyCtxPointer&& other) noexcept; - NCRYPTO_DISALLOW_COPY(EVPKeyCtxPointer) - ~EVPKeyCtxPointer(); - - inline bool operator==(std::nullptr_t) const noexcept { - return ctx_ == nullptr; - } - inline operator bool() const { return ctx_ != nullptr; } - inline EVP_PKEY_CTX* get() const { return ctx_.get(); } - void reset(EVP_PKEY_CTX* ctx = nullptr); - EVP_PKEY_CTX* release(); - - bool initForDerive(const EVPKeyPointer& peer); - DataPointer derive() const; - - bool initForParamgen(); - bool setDhParameters(int prime_size, uint32_t generator); - bool setDsaParameters(uint32_t bits, std::optional q_bits); - bool setEcParameters(int curve, int encoding); - - bool setRsaOaepMd(const Digest& md); - bool setRsaMgf1Md(const Digest& md); - bool setRsaPadding(int padding); - bool setRsaKeygenPubExp(BignumPointer&& e); - bool setRsaKeygenBits(int bits); - bool setRsaPssKeygenMd(const Digest& md); - bool setRsaPssKeygenMgf1Md(const Digest& md); - bool setRsaPssSaltlen(int salt_len); - bool setRsaImplicitRejection(); - bool setRsaOaepLabel(DataPointer&& data); - - bool setSignatureMd(const EVPMDCtxPointer& md); - - bool publicCheck() const; - bool privateCheck() const; - - bool verify(const Buffer& sig, - const Buffer& data); - DataPointer sign(const Buffer& data); - bool signInto(const Buffer& data, - Buffer* sig); - - static constexpr int kDefaultRsaExponent = 0x10001; - - static bool setRsaPadding(EVP_PKEY_CTX* ctx, - int padding, - std::optional salt_len = std::nullopt); - - EVPKeyPointer paramgen() const; - - bool initForEncrypt(); - bool initForDecrypt(); - bool initForKeygen(); - int initForVerify(); - int initForSign(); - - static EVPKeyCtxPointer New(const EVPKeyPointer& key); - static EVPKeyCtxPointer NewFromID(int id); - - private: - DeleteFnPtr ctx_; -}; - -class EVPKeyPointer final { - public: - static EVPKeyPointer New(); - static EVPKeyPointer NewRawPublic(int id, - const Buffer& data); - static EVPKeyPointer NewRawPrivate(int id, - const Buffer& data); -#if OPENSSL_WITH_PQC - static EVPKeyPointer NewRawSeed(int id, - const Buffer& data); -#endif - static EVPKeyPointer NewDH(DHPointer&& dh); - static EVPKeyPointer NewRSA(RSAPointer&& rsa); - - enum class PKEncodingType { - // RSAPublicKey / RSAPrivateKey according to PKCS#1. - PKCS1, - // PrivateKeyInfo or EncryptedPrivateKeyInfo according to PKCS#8. - PKCS8, - // SubjectPublicKeyInfo according to X.509. - SPKI, - // ECPrivateKey according to SEC1. - SEC1, - }; - - enum class PKFormatType { - DER, - PEM, - JWK, - }; - - enum class PKParseError { NOT_RECOGNIZED, NEED_PASSPHRASE, FAILED }; - using ParseKeyResult = Result; - - struct AsymmetricKeyEncodingConfig { - bool output_key_object = false; - PKFormatType format = PKFormatType::DER; - PKEncodingType type = PKEncodingType::PKCS8; - AsymmetricKeyEncodingConfig() = default; - AsymmetricKeyEncodingConfig(bool output_key_object, - PKFormatType format, - PKEncodingType type); - AsymmetricKeyEncodingConfig(const AsymmetricKeyEncodingConfig&) = default; - AsymmetricKeyEncodingConfig& operator=(const AsymmetricKeyEncodingConfig&) = - default; - }; - using PublicKeyEncodingConfig = AsymmetricKeyEncodingConfig; - - struct PrivateKeyEncodingConfig : public AsymmetricKeyEncodingConfig { - const EVP_CIPHER* cipher = nullptr; - std::optional passphrase = std::nullopt; - PrivateKeyEncodingConfig() = default; - PrivateKeyEncodingConfig(bool output_key_object, - PKFormatType format, - PKEncodingType type) - : AsymmetricKeyEncodingConfig(output_key_object, format, type) {} - PrivateKeyEncodingConfig(const PrivateKeyEncodingConfig&); - PrivateKeyEncodingConfig& operator=(const PrivateKeyEncodingConfig&); - }; - - static ParseKeyResult TryParsePublicKey( - const PublicKeyEncodingConfig& config, - const Buffer& buffer); - - static ParseKeyResult TryParsePublicKeyPEM( - const Buffer& buffer); - - static ParseKeyResult TryParsePrivateKey( - const PrivateKeyEncodingConfig& config, - const Buffer& buffer); - - EVPKeyPointer() = default; - explicit EVPKeyPointer(EVP_PKEY* pkey); - EVPKeyPointer(EVPKeyPointer&& other) noexcept; - EVPKeyPointer& operator=(EVPKeyPointer&& other) noexcept; - NCRYPTO_DISALLOW_COPY(EVPKeyPointer) - ~EVPKeyPointer(); - - bool assign(const ECKeyPointer& eckey); - bool set(const ECKeyPointer& eckey); - operator const EC_KEY*() const; - - inline bool operator==(std::nullptr_t) const noexcept { - return pkey_ == nullptr; - } - inline operator bool() const { return pkey_ != nullptr; } - inline EVP_PKEY* get() const { return pkey_.get(); } - void reset(EVP_PKEY* pkey = nullptr); - EVP_PKEY* release(); - - static int id(const EVP_PKEY* key); - static int base_id(const EVP_PKEY* key); - - int id() const; - int base_id() const; - int bits() const; - size_t size() const; - - size_t rawPublicKeySize() const; - size_t rawPrivateKeySize() const; - DataPointer rawPublicKey() const; - DataPointer rawPrivateKey() const; - BIOPointer derPublicKey() const; - -#if OPENSSL_WITH_PQC - DataPointer rawSeed() const; -#endif - - Result writePrivateKey( - const PrivateKeyEncodingConfig& config) const; - Result writePublicKey( - const PublicKeyEncodingConfig& config) const; - - EVPKeyCtxPointer newCtx() const; - - static bool IsRSAPrivateKey(const Buffer& buffer); - - std::optional getBytesOfRS() const; - int getDefaultSignPadding() const; - operator Rsa() const; - operator Dsa() const; - - bool isRsaVariant() const; - bool isOneShotVariant() const; - bool isSigVariant() const; - bool validateDsaParameters() const; - - private: - DeleteFnPtr pkey_; -}; - -class DHPointer final { - public: - enum class FindGroupOption { - NONE, - // There are known and documented security issues with prime groups smaller - // than 2048 bits. When the NO_SMALL_PRIMES option is set, these small prime - // groups will not be supported. - NO_SMALL_PRIMES, - }; - - static BignumPointer GetStandardGenerator(); - - static BignumPointer FindGroup( - std::string_view name, FindGroupOption option = FindGroupOption::NONE); - static DHPointer FromGroup(std::string_view name, - FindGroupOption option = FindGroupOption::NONE); - - static DHPointer New(BignumPointer&& p, BignumPointer&& g); - static DHPointer New(size_t bits, unsigned int generator); - - DHPointer() = default; - explicit DHPointer(DH* dh); - DHPointer(DHPointer&& other) noexcept; - DHPointer& operator=(DHPointer&& other) noexcept; - NCRYPTO_DISALLOW_COPY(DHPointer) - ~DHPointer(); - - inline bool operator==(std::nullptr_t) noexcept { return dh_ == nullptr; } - inline operator bool() const { return dh_ != nullptr; } - inline DH* get() const { return dh_.get(); } - void reset(DH* dh = nullptr); - DH* release(); - - enum class CheckResult { - NONE, - P_NOT_PRIME = DH_CHECK_P_NOT_PRIME, - P_NOT_SAFE_PRIME = DH_CHECK_P_NOT_SAFE_PRIME, - UNABLE_TO_CHECK_GENERATOR = DH_UNABLE_TO_CHECK_GENERATOR, - NOT_SUITABLE_GENERATOR = DH_NOT_SUITABLE_GENERATOR, - Q_NOT_PRIME = DH_CHECK_Q_NOT_PRIME, -#ifndef OPENSSL_IS_BORINGSSL - // Boringssl does not define the DH_CHECK_INVALID_[Q or J]_VALUE - INVALID_Q = DH_CHECK_INVALID_Q_VALUE, - INVALID_J = DH_CHECK_INVALID_J_VALUE, -#endif - CHECK_FAILED = 512, - }; - CheckResult check(); - - enum class CheckPublicKeyResult { - NONE, -#ifndef OPENSSL_IS_BORINGSSL - // Boringssl does not define DH_R_CHECK_PUBKEY_TOO_SMALL or TOO_LARGE - TOO_SMALL = DH_R_CHECK_PUBKEY_TOO_SMALL, - TOO_LARGE = DH_R_CHECK_PUBKEY_TOO_LARGE, - INVALID = DH_R_CHECK_PUBKEY_INVALID, -#else - INVALID = DH_R_INVALID_PUBKEY, -#endif - CHECK_FAILED = 512, - }; - // Check to see if the given public key is suitable for this DH instance. - CheckPublicKeyResult checkPublicKey(const BignumPointer& pub_key); - - DataPointer getPrime() const; - DataPointer getGenerator() const; - DataPointer getPublicKey() const; - DataPointer getPrivateKey() const; - DataPointer generateKeys() const; - DataPointer computeSecret(const BignumPointer& peer) const; - - bool setPublicKey(BignumPointer&& key); - bool setPrivateKey(BignumPointer&& key); - - size_t size() const; - - static DataPointer stateless(const EVPKeyPointer& ourKey, - const EVPKeyPointer& theirKey); - - private: - DeleteFnPtr dh_; -}; - -struct StackOfX509Deleter { - void operator()(STACK_OF(X509) * p) const { sk_X509_pop_free(p, X509_free); } -}; -using StackOfX509 = std::unique_ptr; - -class SSLCtxPointer final { - public: - SSLCtxPointer() = default; - explicit SSLCtxPointer(SSL_CTX* ctx); - SSLCtxPointer(SSLCtxPointer&& other) noexcept; - SSLCtxPointer& operator=(SSLCtxPointer&& other) noexcept; - NCRYPTO_DISALLOW_COPY(SSLCtxPointer) - ~SSLCtxPointer(); - - inline bool operator==(std::nullptr_t) const noexcept { - return ctx_ == nullptr; - } - inline operator bool() const { return ctx_ != nullptr; } - inline SSL_CTX* get() const { return ctx_.get(); } - void reset(SSL_CTX* ctx = nullptr); - void reset(const SSL_METHOD* method); - SSL_CTX* release(); - - bool setGroups(const char* groups); - void setStatusCallback(auto callback) { - if (!ctx_) return; - SSL_CTX_set_tlsext_status_cb(get(), callback); - SSL_CTX_set_tlsext_status_arg(get(), nullptr); - } - - bool setCipherSuites(const char* ciphers); - - static SSLCtxPointer NewServer(); - static SSLCtxPointer NewClient(); - static SSLCtxPointer New(const SSL_METHOD* method = TLS_method()); - - private: - DeleteFnPtr ctx_; -}; - -class SSLPointer final { - public: - SSLPointer() = default; - explicit SSLPointer(SSL* ssl); - SSLPointer(SSLPointer&& other) noexcept; - SSLPointer& operator=(SSLPointer&& other) noexcept; - NCRYPTO_DISALLOW_COPY(SSLPointer) - ~SSLPointer(); - - inline bool operator==(std::nullptr_t) noexcept { return ssl_ == nullptr; } - inline operator bool() const { return ssl_ != nullptr; } - inline SSL* get() const { return ssl_.get(); } - inline operator SSL*() const { return ssl_.get(); } - void reset(SSL* ssl = nullptr); - SSL* release(); - - bool setSession(const SSLSessionPointer& session); - bool setSniContext(const SSLCtxPointer& ctx) const; - - const char* getClientHelloAlpn() const; - const char* getClientHelloServerName() const; - - std::optional getServerName() const; - X509View getCertificate() const; - EVPKeyPointer getPeerTempKey() const; - const SSL_CIPHER* getCipher() const; - bool isServer() const; - - std::optional getCipherName() const; - std::optional getCipherStandardName() const; - std::optional getCipherVersion() const; - - std::optional verifyPeerCertificate() const; - - static std::optional getSecurityLevel(); - - void getCiphers(std::function cb) const; - - static SSLPointer New(const SSLCtxPointer& ctx); - static std::optional GetServerName(const SSL* ssl); - - private: - DeleteFnPtr ssl_; -}; - -class X509Name final { - public: - X509Name(); - explicit X509Name(const X509_NAME* name); - NCRYPTO_DISALLOW_COPY_AND_MOVE(X509Name) - - inline operator const X509_NAME*() const { return name_; } - inline operator bool() const { return name_ != nullptr; } - inline const X509_NAME* get() const { return name_; } - inline size_t size() const { return total_; } - - class Iterator final { - public: - Iterator(const X509Name& name, int pos); - Iterator(const Iterator& other) = default; - Iterator(Iterator&& other) = default; - Iterator& operator=(const Iterator& other) = delete; - Iterator& operator=(Iterator&& other) = delete; - Iterator& operator++(); - operator bool() const; - bool operator==(const Iterator& other) const; - bool operator!=(const Iterator& other) const; - std::pair operator*() const; - - private: - const X509Name& name_; - int loc_; - }; - - inline Iterator begin() const { return Iterator(*this, 0); } - inline Iterator end() const { return Iterator(*this, total_); } - - private: - const X509_NAME* name_; - int total_; -}; - -class X509View final { - public: - static X509View From(const SSLPointer& ssl); - static X509View From(const SSLCtxPointer& ctx); - - X509View() = default; - inline explicit X509View(const X509* cert) : cert_(cert) {} - X509View(const X509View& other) = default; - X509View& operator=(const X509View& other) = default; - NCRYPTO_DISALLOW_MOVE(X509View) - - inline X509* get() const { return const_cast(cert_); } - inline operator X509*() const { return const_cast(cert_); } - inline operator const X509*() const { return cert_; } - - inline bool operator==(std::nullptr_t) noexcept { return cert_ == nullptr; } - inline operator bool() const { return cert_ != nullptr; } - - BIOPointer toPEM() const; - BIOPointer toDER() const; - - const X509Name getSubjectName() const; - const X509Name getIssuerName() const; - BIOPointer getSubject() const; - BIOPointer getSubjectAltName() const; - BIOPointer getIssuer() const; - BIOPointer getInfoAccess() const; - BIOPointer getValidFrom() const; - BIOPointer getValidTo() const; - int64_t getValidFromTime() const; - int64_t getValidToTime() const; - DataPointer getSerialNumber() const; - Result getPublicKey() const; - StackOfASN1 getKeyUsage() const; - - bool isCA() const; - bool isIssuedBy(const X509View& other) const; - bool checkPrivateKey(const EVPKeyPointer& pkey) const; - bool checkPublicKey(const EVPKeyPointer& pkey) const; - - std::optional getFingerprint(const Digest& method) const; - - X509Pointer clone() const; - - enum class CheckMatch { - NO_MATCH, - MATCH, - INVALID_NAME, - OPERATION_FAILED, - }; - CheckMatch checkHost(std::string_view host, - int flags, - DataPointer* peerName = nullptr) const; - CheckMatch checkEmail(std::string_view email, int flags) const; - CheckMatch checkIp(std::string_view ip, int flags) const; - - using UsageCallback = std::function; - bool enumUsages(UsageCallback callback) const; - - template - using KeyCallback = std::function; - bool ifRsa(KeyCallback callback) const; - bool ifEc(KeyCallback callback) const; - - private: - const X509* cert_ = nullptr; -}; - -class X509Pointer final { - public: - static Result Parse(Buffer buffer); - static X509Pointer IssuerFrom(const SSLPointer& ssl, const X509View& view); - static X509Pointer IssuerFrom(const SSL_CTX* ctx, const X509View& view); - static X509Pointer PeerFrom(const SSLPointer& ssl); - - X509Pointer() = default; - explicit X509Pointer(X509* cert); - X509Pointer(X509Pointer&& other) noexcept; - X509Pointer& operator=(X509Pointer&& other) noexcept; - NCRYPTO_DISALLOW_COPY(X509Pointer) - ~X509Pointer(); - - inline bool operator==(std::nullptr_t) noexcept { return cert_ == nullptr; } - inline operator bool() const { return cert_ != nullptr; } - inline X509* get() const { return cert_.get(); } - inline operator X509*() const { return cert_.get(); } - inline operator const X509*() const { return cert_.get(); } - void reset(X509* cert = nullptr); - X509* release(); - - X509View view() const; - operator X509View() const { return view(); } - - static const char* ErrorCode(int32_t err); - static std::optional ErrorReason(int32_t err); - - private: - DeleteFnPtr cert_; -}; - -class ECDSASigPointer final { - public: - explicit ECDSASigPointer(); - explicit ECDSASigPointer(ECDSA_SIG* sig); - ECDSASigPointer(ECDSASigPointer&& other) noexcept; - ECDSASigPointer& operator=(ECDSASigPointer&& other) noexcept; - NCRYPTO_DISALLOW_COPY(ECDSASigPointer) - ~ECDSASigPointer(); - - inline bool operator==(std::nullptr_t) noexcept { return sig_ == nullptr; } - inline operator bool() const { return sig_ != nullptr; } - inline ECDSA_SIG* get() const { return sig_.get(); } - inline operator ECDSA_SIG*() const { return sig_.get(); } - void reset(ECDSA_SIG* sig = nullptr); - ECDSA_SIG* release(); - - static ECDSASigPointer New(); - static ECDSASigPointer Parse(const Buffer& buffer); - - inline const BIGNUM* r() const { return pr_; } - inline const BIGNUM* s() const { return ps_; } - - bool setParams(BignumPointer&& r, BignumPointer&& s); - - Buffer encode() const; - - private: - DeleteFnPtr sig_; - const BIGNUM* pr_ = nullptr; - const BIGNUM* ps_ = nullptr; -}; - -class ECGroupPointer final { - public: - explicit ECGroupPointer(); - explicit ECGroupPointer(EC_GROUP* group); - ECGroupPointer(ECGroupPointer&& other) noexcept; - ECGroupPointer& operator=(ECGroupPointer&& other) noexcept; - NCRYPTO_DISALLOW_COPY(ECGroupPointer) - ~ECGroupPointer(); - - inline bool operator==(std::nullptr_t) noexcept { return group_ == nullptr; } - inline operator bool() const { return group_ != nullptr; } - inline EC_GROUP* get() const { return group_.get(); } - inline operator EC_GROUP*() const { return group_.get(); } - void reset(EC_GROUP* group = nullptr); - EC_GROUP* release(); - - static ECGroupPointer NewByCurveName(int nid); - - private: - DeleteFnPtr group_; -}; - -class ECPointPointer final { - public: - ECPointPointer(); - explicit ECPointPointer(EC_POINT* point); - ECPointPointer(ECPointPointer&& other) noexcept; - ECPointPointer& operator=(ECPointPointer&& other) noexcept; - NCRYPTO_DISALLOW_COPY(ECPointPointer) - ~ECPointPointer(); - - inline bool operator==(std::nullptr_t) noexcept { return point_ == nullptr; } - inline operator bool() const { return point_ != nullptr; } - inline EC_POINT* get() const { return point_.get(); } - inline operator EC_POINT*() const { return point_.get(); } - void reset(EC_POINT* point = nullptr); - EC_POINT* release(); - - bool setFromBuffer(const Buffer& buffer, - const EC_GROUP* group); - bool mul(const EC_GROUP* group, const BIGNUM* priv_key); - - static ECPointPointer New(const EC_GROUP* group); - - private: - DeleteFnPtr point_; -}; - -class ECKeyPointer final { - public: - ECKeyPointer(); - explicit ECKeyPointer(EC_KEY* key); - ECKeyPointer(ECKeyPointer&& other) noexcept; - ECKeyPointer& operator=(ECKeyPointer&& other) noexcept; - NCRYPTO_DISALLOW_COPY(ECKeyPointer) - ~ECKeyPointer(); - - inline bool operator==(std::nullptr_t) noexcept { return key_ == nullptr; } - inline operator bool() const { return key_ != nullptr; } - inline EC_KEY* get() const { return key_.get(); } - inline operator EC_KEY*() const { return key_.get(); } - void reset(EC_KEY* key = nullptr); - EC_KEY* release(); - - ECKeyPointer clone() const; - bool setPrivateKey(const BignumPointer& priv); - bool setPublicKey(const ECPointPointer& pub); - bool setPublicKeyRaw(const BignumPointer& x, const BignumPointer& y); - bool generate(); - bool checkKey() const; - - const EC_GROUP* getGroup() const; - const BIGNUM* getPrivateKey() const; - const EC_POINT* getPublicKey() const; - - static ECKeyPointer New(const EC_GROUP* group); - static ECKeyPointer NewByCurveName(int nid); - - static const EC_POINT* GetPublicKey(const EC_KEY* key); - static const BIGNUM* GetPrivateKey(const EC_KEY* key); - static const EC_GROUP* GetGroup(const EC_KEY* key); - static int GetGroupName(const EC_KEY* key); - static bool Check(const EC_KEY* key); - - private: - DeleteFnPtr key_; -}; - -class EVPMDCtxPointer final { - public: - EVPMDCtxPointer(); - explicit EVPMDCtxPointer(EVP_MD_CTX* ctx); - EVPMDCtxPointer(EVPMDCtxPointer&& other) noexcept; - EVPMDCtxPointer& operator=(EVPMDCtxPointer&& other) noexcept; - NCRYPTO_DISALLOW_COPY(EVPMDCtxPointer) - ~EVPMDCtxPointer(); - - inline bool operator==(std::nullptr_t) noexcept { return ctx_ == nullptr; } - inline operator bool() const { return ctx_ != nullptr; } - inline EVP_MD_CTX* get() const { return ctx_.get(); } - inline operator EVP_MD_CTX*() const { return ctx_.get(); } - void reset(EVP_MD_CTX* ctx = nullptr); - EVP_MD_CTX* release(); - - bool digestInit(const Digest& digest); - bool digestUpdate(const Buffer& in); - DataPointer digestFinal(size_t length); - bool digestFinalInto(Buffer* buf); - size_t getExpectedSize(); - - std::optional signInit(const EVPKeyPointer& key, - const Digest& digest); - std::optional verifyInit(const EVPKeyPointer& key, - const Digest& digest); - - DataPointer signOneShot(const Buffer& buf) const; - DataPointer sign(const Buffer& buf) const; - bool verify(const Buffer& buf, - const Buffer& sig) const; - - const EVP_MD* getDigest() const; - size_t getDigestSize() const; - bool hasXofFlag() const; - - bool copyTo(const EVPMDCtxPointer& other) const; - - static EVPMDCtxPointer New(); - - private: - DeleteFnPtr ctx_; -}; - -class HMACCtxPointer final { - public: - HMACCtxPointer(); - explicit HMACCtxPointer(HMAC_CTX* ctx); - HMACCtxPointer(HMACCtxPointer&& other) noexcept; - HMACCtxPointer& operator=(HMACCtxPointer&& other) noexcept; - NCRYPTO_DISALLOW_COPY(HMACCtxPointer) - ~HMACCtxPointer(); - - inline bool operator==(std::nullptr_t) noexcept { return ctx_ == nullptr; } - inline operator bool() const { return ctx_ != nullptr; } - inline HMAC_CTX* get() const { return ctx_.get(); } - inline operator HMAC_CTX*() const { return ctx_.get(); } - void reset(HMAC_CTX* ctx = nullptr); - HMAC_CTX* release(); - - bool init(const Buffer& buf, const Digest& md); - bool update(const Buffer& buf); - DataPointer digest(); - bool digestInto(Buffer* buf); - - static HMACCtxPointer New(); - - private: - DeleteFnPtr ctx_; -}; - -#ifndef OPENSSL_NO_ENGINE -class EnginePointer final { - public: - EnginePointer() = default; - - explicit EnginePointer(ENGINE* engine_, bool finish_on_exit = false); - EnginePointer(EnginePointer&& other) noexcept; - EnginePointer& operator=(EnginePointer&& other) noexcept; - NCRYPTO_DISALLOW_COPY(EnginePointer) - ~EnginePointer(); - - inline operator bool() const { return engine != nullptr; } - inline ENGINE* get() { return engine; } - inline void setFinishOnExit() { finish_on_exit = true; } - - void reset(ENGINE* engine_ = nullptr, bool finish_on_exit_ = false); - - bool setAsDefault(uint32_t flags, CryptoErrorList* errors = nullptr); - bool init(bool finish_on_exit = false); - EVPKeyPointer loadPrivateKey(const char* key_name); - - // Release ownership of the ENGINE* pointer. - ENGINE* release(); - - // Retrieve an OpenSSL Engine instance by name. If the name does not - // identify a valid named engine, the returned EnginePointer will be - // empty. - static EnginePointer getEngineByName(const char* name, - CryptoErrorList* errors = nullptr); - - // Call once when initializing OpenSSL at startup for the process. - static void initEnginesOnce(); - - private: - ENGINE* engine = nullptr; - bool finish_on_exit = false; -}; -#endif // !OPENSSL_NO_ENGINE - -// ============================================================================ -// FIPS -bool isFipsEnabled(); - -bool setFipsEnabled(bool enabled, CryptoErrorList* errors); - -bool testFipsEnabled(); - -// ============================================================================ -// Various utilities - -bool CSPRNG(void* buffer, size_t length) NCRYPTO_MUST_USE_RESULT; - -// This callback is used to avoid the default passphrase callback in OpenSSL -// which will typically prompt for the passphrase. The prompting is designed -// for the OpenSSL CLI, but works poorly for some environments like Node.js -// because it involves synchronous interaction with the controlling terminal, -// something we never want, and use this function to avoid it. -int NoPasswordCallback(char* buf, int size, int rwflag, void* u); - -int PasswordCallback(char* buf, int size, int rwflag, void* u); - -bool SafeX509SubjectAltNamePrint(const BIOPointer& out, X509_EXTENSION* ext); -bool SafeX509InfoAccessPrint(const BIOPointer& out, X509_EXTENSION* ext); - -// ============================================================================ -// SPKAC - -bool VerifySpkac(const char* input, size_t length); -BIOPointer ExportPublicKey(const char* input, size_t length); - -// The caller takes ownership of the returned Buffer -Buffer ExportChallenge(const char* input, size_t length); - -// ============================================================================ -// KDF - -const EVP_MD* getDigestByName(const char* name); -const EVP_CIPHER* getCipherByName(const char* name); - -// Verify that the specified HKDF output length is valid for the given digest. -// The maximum length for HKDF output for a given digest is 255 times the -// hash size for the given digest algorithm. -bool checkHkdfLength(const Digest& digest, size_t length); - -bool extractP1363(const Buffer& buf, - unsigned char* dest, - size_t n); - -DataPointer hkdf(const Digest& md, - const Buffer& key, - const Buffer& info, - const Buffer& salt, - size_t length); - -bool checkScryptParams(uint64_t N, uint64_t r, uint64_t p, uint64_t maxmem); - -DataPointer scrypt(const Buffer& pass, - const Buffer& salt, - uint64_t N, - uint64_t r, - uint64_t p, - uint64_t maxmem, - size_t length); - -DataPointer pbkdf2(const Digest& md, - const Buffer& pass, - const Buffer& salt, - uint32_t iterations, - size_t length); - -#if OPENSSL_VERSION_NUMBER >= 0x30200000L -#ifndef OPENSSL_NO_ARGON2 -enum class Argon2Type { ARGON2D, ARGON2I, ARGON2ID }; - -DataPointer argon2(const Buffer& pass, - const Buffer& salt, - uint32_t lanes, - size_t length, - uint32_t memcost, - uint32_t iter, - uint32_t version, - const Buffer& secret, - const Buffer& ad, - Argon2Type type); -#endif -#endif - -// ============================================================================ -// KEM (Key Encapsulation Mechanism) -#if OPENSSL_VERSION_MAJOR >= 3 - -class KEM final { - public: - struct EncapsulateResult { - DataPointer ciphertext; - DataPointer shared_key; - - EncapsulateResult() = default; - EncapsulateResult(DataPointer ct, DataPointer sk) - : ciphertext(std::move(ct)), shared_key(std::move(sk)) {} - }; - - // Encapsulate a shared secret using KEM with a public key. - // Returns both the ciphertext and shared secret. - static std::optional Encapsulate( - const EVPKeyPointer& public_key); - - // Decapsulate a shared secret using KEM with a private key and ciphertext. - // Returns the shared secret. - static DataPointer Decapsulate(const EVPKeyPointer& private_key, - const Buffer& ciphertext); - - private: -#if !OPENSSL_VERSION_PREREQ(3, 5) - static bool SetOperationParameter(EVP_PKEY_CTX* ctx, - const EVPKeyPointer& key); -#endif -}; - -#endif // OPENSSL_VERSION_MAJOR >= 3 - -// ============================================================================ -// Version metadata -#define NCRYPTO_VERSION "0.0.1" - -enum { - NCRYPTO_VERSION_MAJOR = 0, - NCRYPTO_VERSION_MINOR = 0, - NCRYPTO_VERSION_REVISION = 1, -}; - -} // namespace ncrypto diff --git a/packages/react-native-quick-crypto/nitro.json b/packages/react-native-quick-crypto/nitro.json index 4c0a0edd..e2ed912e 100644 --- a/packages/react-native-quick-crypto/nitro.json +++ b/packages/react-native-quick-crypto/nitro.json @@ -21,7 +21,8 @@ "RsaCipher": { "cpp": "HybridRsaCipher" }, "RsaKeyPair": { "cpp": "HybridRsaKeyPair" }, "SignHandle": { "cpp": "HybridSignHandle" }, - "VerifyHandle": { "cpp": "HybridVerifyHandle" } + "VerifyHandle": { "cpp": "HybridVerifyHandle" }, + "MlDsaKeyPair": { "cpp": "HybridMlDsaKeyPair" } }, "ignorePaths": ["node_modules", "lib"] } diff --git a/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCrypto+autolinking.cmake b/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCrypto+autolinking.cmake index 881e0b40..c8d148c6 100644 --- a/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCrypto+autolinking.cmake +++ b/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCrypto+autolinking.cmake @@ -35,6 +35,7 @@ target_sources( ../nitrogen/generated/shared/c++/HybridHashSpec.cpp ../nitrogen/generated/shared/c++/HybridHmacSpec.cpp ../nitrogen/generated/shared/c++/HybridKeyObjectHandleSpec.cpp + ../nitrogen/generated/shared/c++/HybridMlDsaKeyPairSpec.cpp ../nitrogen/generated/shared/c++/HybridPbkdf2Spec.cpp ../nitrogen/generated/shared/c++/HybridRandomSpec.cpp ../nitrogen/generated/shared/c++/HybridRsaCipherSpec.cpp diff --git a/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCryptoOnLoad.cpp b/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCryptoOnLoad.cpp index 2e991d8a..95014ab4 100644 --- a/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCryptoOnLoad.cpp +++ b/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCryptoOnLoad.cpp @@ -29,6 +29,7 @@ #include "HybridRsaKeyPair.hpp" #include "HybridSignHandle.hpp" #include "HybridVerifyHandle.hpp" +#include "HybridMlDsaKeyPair.hpp" namespace margelo::nitro::crypto { @@ -168,6 +169,15 @@ int initialize(JavaVM* vm) { return std::make_shared(); } ); + HybridObjectRegistry::registerHybridObjectConstructor( + "MlDsaKeyPair", + []() -> std::shared_ptr { + static_assert(std::is_default_constructible_v, + "The HybridObject \"HybridMlDsaKeyPair\" is not default-constructible! " + "Create a public constructor that takes zero arguments to be able to autolink this HybridObject."); + return std::make_shared(); + } + ); }); } diff --git a/packages/react-native-quick-crypto/nitrogen/generated/ios/QuickCryptoAutolinking.mm b/packages/react-native-quick-crypto/nitrogen/generated/ios/QuickCryptoAutolinking.mm index 8086bccf..c3aec3b2 100644 --- a/packages/react-native-quick-crypto/nitrogen/generated/ios/QuickCryptoAutolinking.mm +++ b/packages/react-native-quick-crypto/nitrogen/generated/ios/QuickCryptoAutolinking.mm @@ -24,6 +24,7 @@ #include "HybridRsaKeyPair.hpp" #include "HybridSignHandle.hpp" #include "HybridVerifyHandle.hpp" +#include "HybridMlDsaKeyPair.hpp" @interface QuickCryptoAutolinking : NSObject @end @@ -160,6 +161,15 @@ + (void) load { return std::make_shared(); } ); + HybridObjectRegistry::registerHybridObjectConstructor( + "MlDsaKeyPair", + []() -> std::shared_ptr { + static_assert(std::is_default_constructible_v, + "The HybridObject \"HybridMlDsaKeyPair\" is not default-constructible! " + "Create a public constructor that takes zero arguments to be able to autolink this HybridObject."); + return std::make_shared(); + } + ); } @end diff --git a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/AsymmetricKeyType.hpp b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/AsymmetricKeyType.hpp index f48d215d..ab6e7669 100644 --- a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/AsymmetricKeyType.hpp +++ b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/AsymmetricKeyType.hpp @@ -38,6 +38,9 @@ namespace margelo::nitro::crypto { ED448 SWIFT_NAME(ed448) = 6, X25519 SWIFT_NAME(x25519) = 7, X448 SWIFT_NAME(x448) = 8, + ML_DSA_44 SWIFT_NAME(mlDsa44) = 9, + ML_DSA_65 SWIFT_NAME(mlDsa65) = 10, + ML_DSA_87 SWIFT_NAME(mlDsa87) = 11, } CLOSED_ENUM; } // namespace margelo::nitro::crypto @@ -59,6 +62,9 @@ namespace margelo::nitro { case hashString("ed448"): return margelo::nitro::crypto::AsymmetricKeyType::ED448; case hashString("x25519"): return margelo::nitro::crypto::AsymmetricKeyType::X25519; case hashString("x448"): return margelo::nitro::crypto::AsymmetricKeyType::X448; + case hashString("ml-dsa-44"): return margelo::nitro::crypto::AsymmetricKeyType::ML_DSA_44; + case hashString("ml-dsa-65"): return margelo::nitro::crypto::AsymmetricKeyType::ML_DSA_65; + case hashString("ml-dsa-87"): return margelo::nitro::crypto::AsymmetricKeyType::ML_DSA_87; default: [[unlikely]] throw std::invalid_argument("Cannot convert \"" + unionValue + "\" to enum AsymmetricKeyType - invalid value!"); } @@ -74,6 +80,9 @@ namespace margelo::nitro { case margelo::nitro::crypto::AsymmetricKeyType::ED448: return JSIConverter::toJSI(runtime, "ed448"); case margelo::nitro::crypto::AsymmetricKeyType::X25519: return JSIConverter::toJSI(runtime, "x25519"); case margelo::nitro::crypto::AsymmetricKeyType::X448: return JSIConverter::toJSI(runtime, "x448"); + case margelo::nitro::crypto::AsymmetricKeyType::ML_DSA_44: return JSIConverter::toJSI(runtime, "ml-dsa-44"); + case margelo::nitro::crypto::AsymmetricKeyType::ML_DSA_65: return JSIConverter::toJSI(runtime, "ml-dsa-65"); + case margelo::nitro::crypto::AsymmetricKeyType::ML_DSA_87: return JSIConverter::toJSI(runtime, "ml-dsa-87"); default: [[unlikely]] throw std::invalid_argument("Cannot convert AsymmetricKeyType to JS - invalid value: " + std::to_string(static_cast(arg)) + "!"); @@ -94,6 +103,9 @@ namespace margelo::nitro { case hashString("ed448"): case hashString("x25519"): case hashString("x448"): + case hashString("ml-dsa-44"): + case hashString("ml-dsa-65"): + case hashString("ml-dsa-87"): return true; default: return false; diff --git a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridMlDsaKeyPairSpec.cpp b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridMlDsaKeyPairSpec.cpp new file mode 100644 index 00000000..1a5b1096 --- /dev/null +++ b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridMlDsaKeyPairSpec.cpp @@ -0,0 +1,29 @@ +/// +/// HybridMlDsaKeyPairSpec.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#include "HybridMlDsaKeyPairSpec.hpp" + +namespace margelo::nitro::crypto { + + void HybridMlDsaKeyPairSpec::loadHybridMethods() { + // load base methods/properties + HybridObject::loadHybridMethods(); + // load custom methods/properties + registerHybrids(this, [](Prototype& prototype) { + prototype.registerHybridMethod("generateKeyPair", &HybridMlDsaKeyPairSpec::generateKeyPair); + prototype.registerHybridMethod("generateKeyPairSync", &HybridMlDsaKeyPairSpec::generateKeyPairSync); + prototype.registerHybridMethod("getPublicKey", &HybridMlDsaKeyPairSpec::getPublicKey); + prototype.registerHybridMethod("getPrivateKey", &HybridMlDsaKeyPairSpec::getPrivateKey); + prototype.registerHybridMethod("sign", &HybridMlDsaKeyPairSpec::sign); + prototype.registerHybridMethod("signSync", &HybridMlDsaKeyPairSpec::signSync); + prototype.registerHybridMethod("verify", &HybridMlDsaKeyPairSpec::verify); + prototype.registerHybridMethod("verifySync", &HybridMlDsaKeyPairSpec::verifySync); + prototype.registerHybridMethod("setVariant", &HybridMlDsaKeyPairSpec::setVariant); + }); + } + +} // namespace margelo::nitro::crypto diff --git a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridMlDsaKeyPairSpec.hpp b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridMlDsaKeyPairSpec.hpp new file mode 100644 index 00000000..97301c6a --- /dev/null +++ b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridMlDsaKeyPairSpec.hpp @@ -0,0 +1,73 @@ +/// +/// HybridMlDsaKeyPairSpec.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + +// Forward declaration of `ArrayBuffer` to properly resolve imports. +namespace NitroModules { class ArrayBuffer; } + +#include +#include +#include + +namespace margelo::nitro::crypto { + + using namespace margelo::nitro; + + /** + * An abstract base class for `MlDsaKeyPair` + * Inherit this class to create instances of `HybridMlDsaKeyPairSpec` in C++. + * You must explicitly call `HybridObject`'s constructor yourself, because it is virtual. + * @example + * ```cpp + * class HybridMlDsaKeyPair: public HybridMlDsaKeyPairSpec { + * public: + * HybridMlDsaKeyPair(...): HybridObject(TAG) { ... } + * // ... + * }; + * ``` + */ + class HybridMlDsaKeyPairSpec: public virtual HybridObject { + public: + // Constructor + explicit HybridMlDsaKeyPairSpec(): HybridObject(TAG) { } + + // Destructor + ~HybridMlDsaKeyPairSpec() override = default; + + public: + // Properties + + + public: + // Methods + virtual std::shared_ptr> generateKeyPair(double publicFormat, double publicType, double privateFormat, double privateType) = 0; + virtual void generateKeyPairSync(double publicFormat, double publicType, double privateFormat, double privateType) = 0; + virtual std::shared_ptr getPublicKey() = 0; + virtual std::shared_ptr getPrivateKey() = 0; + virtual std::shared_ptr>> sign(const std::shared_ptr& message) = 0; + virtual std::shared_ptr signSync(const std::shared_ptr& message) = 0; + virtual std::shared_ptr> verify(const std::shared_ptr& signature, const std::shared_ptr& message) = 0; + virtual bool verifySync(const std::shared_ptr& signature, const std::shared_ptr& message) = 0; + virtual void setVariant(const std::string& variant) = 0; + + protected: + // Hybrid Setup + void loadHybridMethods() override; + + protected: + // Tag for logging + static constexpr auto TAG = "MlDsaKeyPair"; + }; + +} // namespace margelo::nitro::crypto diff --git a/packages/react-native-quick-crypto/src/mldsa.ts b/packages/react-native-quick-crypto/src/mldsa.ts new file mode 100644 index 00000000..0c9234e7 --- /dev/null +++ b/packages/react-native-quick-crypto/src/mldsa.ts @@ -0,0 +1,125 @@ +import { NitroModules } from 'react-native-nitro-modules'; +import type { MlDsaKeyPair } from './specs/mlDsaKeyPair.nitro'; +import { + CryptoKey, + KeyObject, + PublicKeyObject, + PrivateKeyObject as PrivateKeyObjectClass, +} from './keys'; +import type { CryptoKeyPair, KeyUsage, SubtleAlgorithm } from './utils'; +import { + hasAnyNotIn, + lazyDOMException, + getUsagesUnion, + KFormatType, + KeyEncoding, +} from './utils'; + +export type MlDsaVariant = 'ML-DSA-44' | 'ML-DSA-65' | 'ML-DSA-87'; + +export class MlDsa { + variant: MlDsaVariant; + native: MlDsaKeyPair; + + constructor(variant: MlDsaVariant) { + this.variant = variant; + this.native = NitroModules.createHybridObject('MlDsaKeyPair'); + this.native.setVariant(variant); + } + + async generateKeyPair(): Promise { + await this.native.generateKeyPair( + KFormatType.DER, + KeyEncoding.SPKI, + KFormatType.DER, + KeyEncoding.PKCS8, + ); + } + + generateKeyPairSync(): void { + this.native.generateKeyPairSync( + KFormatType.DER, + KeyEncoding.SPKI, + KFormatType.DER, + KeyEncoding.PKCS8, + ); + } + + getPublicKey(): ArrayBuffer { + return this.native.getPublicKey(); + } + + getPrivateKey(): ArrayBuffer { + return this.native.getPrivateKey(); + } + + async sign(message: ArrayBuffer): Promise { + return this.native.sign(message); + } + + signSync(message: ArrayBuffer): ArrayBuffer { + return this.native.signSync(message); + } + + async verify(signature: ArrayBuffer, message: ArrayBuffer): Promise { + return this.native.verify(signature, message); + } + + verifySync(signature: ArrayBuffer, message: ArrayBuffer): boolean { + return this.native.verifySync(signature, message); + } +} + +export async function mldsa_generateKeyPairWebCrypto( + variant: MlDsaVariant, + extractable: boolean, + keyUsages: KeyUsage[], +): Promise { + if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) { + throw lazyDOMException( + `Unsupported key usage for ${variant}`, + 'SyntaxError', + ); + } + + const publicUsages = getUsagesUnion(keyUsages, 'verify'); + const privateUsages = getUsagesUnion(keyUsages, 'sign'); + + if (privateUsages.length === 0) { + throw lazyDOMException('Usages cannot be empty', 'SyntaxError'); + } + + const mldsa = new MlDsa(variant); + await mldsa.generateKeyPair(); + + const publicKeyData = mldsa.getPublicKey(); + const privateKeyData = mldsa.getPrivateKey(); + + const pub = KeyObject.createKeyObject( + 'public', + publicKeyData, + KFormatType.DER, + KeyEncoding.SPKI, + ) as PublicKeyObject; + const publicKey = new CryptoKey( + pub, + { name: variant } as SubtleAlgorithm, + publicUsages, + true, + ); + + const priv = KeyObject.createKeyObject( + 'private', + privateKeyData, + KFormatType.DER, + KeyEncoding.PKCS8, + ) as PrivateKeyObjectClass; + const privateKey = new CryptoKey( + priv, + { name: variant } as SubtleAlgorithm, + privateUsages, + extractable, + ); + + return { publicKey, privateKey }; +} diff --git a/packages/react-native-quick-crypto/src/specs/mlDsaKeyPair.nitro.ts b/packages/react-native-quick-crypto/src/specs/mlDsaKeyPair.nitro.ts new file mode 100644 index 00000000..d7bfbcb2 --- /dev/null +++ b/packages/react-native-quick-crypto/src/specs/mlDsaKeyPair.nitro.ts @@ -0,0 +1,29 @@ +import type { HybridObject } from 'react-native-nitro-modules'; + +export interface MlDsaKeyPair + extends HybridObject<{ ios: 'c++'; android: 'c++' }> { + generateKeyPair( + publicFormat: number, + publicType: number, + privateFormat: number, + privateType: number, + ): Promise; + + generateKeyPairSync( + publicFormat: number, + publicType: number, + privateFormat: number, + privateType: number, + ): void; + + getPublicKey(): ArrayBuffer; + getPrivateKey(): ArrayBuffer; + + sign(message: ArrayBuffer): Promise; + signSync(message: ArrayBuffer): ArrayBuffer; + + verify(signature: ArrayBuffer, message: ArrayBuffer): Promise; + verifySync(signature: ArrayBuffer, message: ArrayBuffer): boolean; + + setVariant(variant: string): void; +} diff --git a/packages/react-native-quick-crypto/src/subtle.ts b/packages/react-native-quick-crypto/src/subtle.ts index e9c38396..540dc270 100644 --- a/packages/react-native-quick-crypto/src/subtle.ts +++ b/packages/react-native-quick-crypto/src/subtle.ts @@ -42,6 +42,7 @@ import { getRandomValues } from './random'; import { createHmac } from './hmac'; import { createSign, createVerify } from './keys/signVerify'; import { ed_generateKeyPairWebCrypto, Ed } from './ed'; +import { mldsa_generateKeyPairWebCrypto, type MlDsaVariant } from './mldsa'; // import { pbkdf2DeriveBits } from './pbkdf2'; // import { aesCipher, aesGenerateKey, aesImportKey, getAlgorithmName } from './aes'; // import { rsaCipher, rsaExportKey, rsaImportKey, rsaKeyGenerate } from './rsa'; @@ -782,6 +783,53 @@ function edImportKey( return new CryptoKey(keyObject, { name }, keyUsages, extractable); } +function mldsaImportKey( + format: ImportFormat, + data: BufferLike, + algorithm: SubtleAlgorithm, + extractable: boolean, + keyUsages: KeyUsage[], +): CryptoKey { + const { name } = algorithm; + + // Validate usages + if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) { + throw lazyDOMException( + `Unsupported key usage for ${name} key`, + 'SyntaxError', + ); + } + + let keyObject: KeyObject; + + if (format === 'spki') { + // Import public key + const keyData = bufferLikeToArrayBuffer(data); + keyObject = KeyObject.createKeyObject( + 'public', + keyData, + KFormatType.DER, + KeyEncoding.SPKI, + ); + } else if (format === 'pkcs8') { + // Import private key + const keyData = bufferLikeToArrayBuffer(data); + keyObject = KeyObject.createKeyObject( + 'private', + keyData, + KFormatType.DER, + KeyEncoding.PKCS8, + ); + } else { + throw lazyDOMException( + `Unsupported format for ${name} import: ${format}`, + 'NotSupportedError', + ); + } + + return new CryptoKey(keyObject, { name }, keyUsages, extractable); +} + const exportKeySpki = async ( key: CryptoKey, ): Promise => { @@ -812,6 +860,18 @@ const exportKeySpki = async ( ); } break; + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': + if (key.type === 'public') { + // Export ML-DSA key in SPKI DER format + return bufferLikeToArrayBuffer( + key.keyObject.handle.exportKey(KFormatType.DER, KeyEncoding.SPKI), + ); + } + break; } throw new Error( @@ -849,6 +909,18 @@ const exportKeyPkcs8 = async ( ); } break; + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': + if (key.type === 'private') { + // Export ML-DSA key in PKCS8 DER format + return bufferLikeToArrayBuffer( + key.keyObject.handle.exportKey(KFormatType.DER, KeyEncoding.PKCS8), + ); + } + break; } throw new Error( @@ -1121,6 +1193,36 @@ function edSignVerify( } } +function mldsaSignVerify( + key: CryptoKey, + data: BufferLike, + signature?: BufferLike, +): ArrayBuffer | boolean { + const isSign = signature === undefined; + const expectedKeyType = isSign ? 'private' : 'public'; + + if (key.type !== expectedKeyType) { + throw lazyDOMException( + `Key must be a ${expectedKeyType} key`, + 'InvalidAccessError', + ); + } + + const dataBuffer = bufferLikeToArrayBuffer(data); + + if (isSign) { + const signer = createSign(''); + signer.update(dataBuffer); + const sig = signer.sign({ key: key }); + return sig.buffer.slice(sig.byteOffset, sig.byteOffset + sig.byteLength); + } else { + const signatureBuffer = bufferLikeToArrayBuffer(signature!); + const verifier = createVerify(''); + verifier.update(dataBuffer); + return verifier.verify({ key: key }, signatureBuffer); + } +} + const signVerify = ( algorithm: SubtleAlgorithm, key: CryptoKey, @@ -1149,6 +1251,10 @@ const signVerify = ( case 'Ed25519': case 'Ed448': return edSignVerify(key, data, signature); + case 'ML-DSA-44': + case 'ML-DSA-65': + case 'ML-DSA-87': + return mldsaSignVerify(key, data, signature); } throw lazyDOMException( `Unrecognized algorithm name '${algorithm.name}' for '${usage}'`, @@ -1318,6 +1424,18 @@ export class Subtle { ); checkCryptoKeyPairUsages(result as CryptoKeyPair); break; + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': + result = await mldsa_generateKeyPairWebCrypto( + algorithm.name as MlDsaVariant, + extractable, + keyUsages, + ); + checkCryptoKeyPairUsages(result as CryptoKeyPair); + break; default: throw new Error( `'subtle.generateKey()' is not implemented for ${algorithm.name}. @@ -1406,6 +1524,19 @@ export class Subtle { keyUsages, ); break; + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': + result = mldsaImportKey( + format, + data as BufferLike, + normalizedAlgorithm, + extractable, + keyUsages, + ); + break; default: throw new Error( `"subtle.importKey()" is not implemented for ${normalizedAlgorithm.name}`, diff --git a/packages/react-native-quick-crypto/src/utils/types.ts b/packages/react-native-quick-crypto/src/utils/types.ts index c74cc1c8..164a68d6 100644 --- a/packages/react-native-quick-crypto/src/utils/types.ts +++ b/packages/react-native-quick-crypto/src/utils/types.ts @@ -65,6 +65,9 @@ export type ECKeyPairAlgorithm = 'ECDSA' | 'ECDH'; export type CFRGKeyPairAlgorithm = 'Ed25519' | 'Ed448' | 'X25519' | 'X448'; export type CFRGKeyPairType = 'ed25519' | 'ed448' | 'x25519' | 'x448'; +export type PQCKeyPairAlgorithm = 'ML-DSA-44' | 'ML-DSA-65' | 'ML-DSA-87'; +export type PQCKeyPairType = 'ml-dsa-44' | 'ml-dsa-65' | 'ml-dsa-87'; + // Node.js style key pair types (lowercase) export type RSAKeyPairType = 'rsa' | 'rsa-pss'; export type ECKeyPairType = 'ec'; @@ -74,7 +77,8 @@ export type DHKeyPairType = 'dh'; export type KeyPairAlgorithm = | RSAKeyPairAlgorithm | ECKeyPairAlgorithm - | CFRGKeyPairAlgorithm; + | CFRGKeyPairAlgorithm + | PQCKeyPairAlgorithm; export type AESAlgorithm = 'AES-CTR' | 'AES-CBC' | 'AES-GCM' | 'AES-KW'; @@ -86,7 +90,10 @@ export type SignVerifyAlgorithm = | 'ECDSA' | 'HMAC' | 'Ed25519' - | 'Ed448'; + | 'Ed448' + | 'ML-DSA-44' + | 'ML-DSA-65' + | 'ML-DSA-87'; export type DeriveBitsAlgorithm = | 'PBKDF2' @@ -243,7 +250,8 @@ export type AsymmetricKeyType = | 'dsa' | 'ec' | 'dh' - | CFRGKeyPairType; + | CFRGKeyPairType + | PQCKeyPairType; type JWKkty = 'AES' | 'RSA' | 'EC' | 'oct'; type JWKuse = 'sig' | 'enc';