okta / okta-sdk-java

A Java SDK for interacting with the Okta management API, enabling server-side code to manage Okta users, groups, applications, and more.
Apache License 2.0
145 stars 136 forks source link

Private Key is interpreted as PEMKeyPair instead of PrivateKeyInfo #1024

Open ITSamMed opened 5 months ago

ITSamMed commented 5 months ago

Describe the bug?

The PrivateKey parameter does not seem to be handled correctly for the OktaClient builder when setting directly.

Instead of treating the private key as a PrivateKey, it is getting treated as as PEMKeyPair.

What is expected to happen?

OktaClient is built successfully

PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) pemContent;
privateKey = jcaPEMKeyConverter.getPrivateKey(privateKeyInfo);

What is the actual behavior?

Exception is thrown

Cannot invoke "org.bouncycastle.asn1.x509.SubjectPublicKeyInfo.getEncoded()" because the return value of "org.bouncycastle.openssl.PEMKeyPair.getPublicKeyInfo()" is null

PEMKeyPair pemKeyPair = (PEMKeyPair) pemContent;
KeyPair keyPair = jcaPEMKeyConverter.getKeyPair(pemKeyPair);
privateKey = keyPair.getPrivate();

Reproduction Steps?

  1. JJWT
    val keyPair = Jwts.SIG.ES512.keyPair().build()
    Clients.builder().setPrivateKey(keyPair.privateKey).build()
  2. Java KeyStore
    
    keytool -alias <alias> -genkeypair -keyalg ec -groupname secp521r1 -keystore <keystore>.p12 -storetype pkcs12
    val type = "pkcs12"
    val password = <password>
    val path = <path>
    val alias = <alias>
    val keyStore =
    try {
        KeyStore.getInstance(type)
    } catch (ex: KeyStoreException) {
        println("No keystore provider of type '${type}'.")
        throw ex
    }

try { FileInputStream(path).use { keyStore.load(it, password.toCharArray()) } } catch (ex: Exception) { when (ex) { is FileNotFoundException -> println("Keystore not found by path '${path}'.") is IOException -> println("Incorrect password for keystore.") } throw ex }

// Assumes password is the same for keystore and key entry. val key = keyStore.getKey(alias, password.toCharArray()) if (key == null) { println("Keystore entry not found by alias '${alias}'.") throw UnrecoverableKeyException("Keystore entry not found by alias '${alias}'.") }

// Assumes alias is the same for private and public key entry. val certificate = keyStore.getCertificate(alias)

val keyPair = KeyPair(certificate.publicKey, key as PrivateKey)

Clients.builder().setPrivateKey(keyPair.privateKey).build()

3. OpenSSL + BouncyCastle

openssl genpkey -out .key -algorithm EC -pkeyopt ec_paramgen_curve:P-521 -aes-128-cbc val privateKey: PrivateKey

FileReader(File("")).use { keyReader ->

val parser = PEMParser(keyReader)
val pair = parser.readObject() as PKCS8EncryptedPrivateKeyInfo
val jce = JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider(BouncyCastleProvider())
val decProv = jce.build(<password>.toCharArray())
val info = pair.decryptPrivateKeyInfo(decProv)
val converter = JcaPEMKeyConverter()
privateKey = converter.getPrivateKey(info)

}

Clients.builder().setPrivateKey(privateKey).build()



### Additional Information?

_No response_

### Java Version

openjdk version "17.0.10" 2024-01-16
OpenJDK Runtime Environment Temurin-17.0.10+7 (build 17.0.10+7)
OpenJDK 64-Bit Server VM Temurin-17.0.10+7 (build 17.0.10+7, mixed mode, sharing)

### SDK Version

implementation("com.okta.sdk:okta-sdk-api:16.0.0")
runtimeOnly("com.okta.sdk:okta-sdk-impl:16.0.0")

### OS version

|BuildNumber|Caption|OSArchitecture|Version|
|---|---|---|---|
|19045|Microsoft Windows 10 Enterprise|64-bit|10.0.19045|
ITSamMed commented 5 months ago

It seems like this may be related specifically to Elliptic Curve Digital Signature Algorithm (ECDSA).

This builds just fine: Clients.builder().setPrivateKey(Jwts.SIG.RS256.keyPair().build().private).build() This fails to build: Clients.builder().setPrivateKey(Jwts.SIG.ES256.keyPair().build().private).build()

ITSamMed commented 5 months ago

What's weird is that passing an unencrypted PEM file path whose private key is ES512 also seems to work...

openssl genpkey -out unencrypted.key -algorithm EC -pkeyopt ec_paramgen_curve:P-521 Clients.builder().setPrivateKey("unencrypted.key").build()

arvindkrishnakumar-okta commented 5 months ago

@ITSamMed Thanks for posting! I'd take a look.