Uploaded image for project: 'OpenAM'
  1. OpenAM
  2. OPENAM-10353

JWKs list in Jwks_uri is only loading the "Token Signing RSA public/private key pair" certificate

    Details

    • Type: Bug
    • Status: Resolved
    • Priority: Major
    • Resolution: Fixed
    • Affects Version/s: 13.5.0, 14.0.0
    • Fix Version/s: 13.5.1, 14.0.0
    • Component/s: oauth2
    • Labels:
    • Sprint:
      AM Sustaining Sprint 33
    • Story Points:
      3
    • Support Ticket IDs:

      Description

      \When using the default Jwk_uri (http://openam.example.com:13081/openam/oauth2/connect/jwk_uri), OpenAM will load the JWK from two properties:

      • "Token Signing RSA public/private key pair"
      • "Token Signing ECDSA public/private key pair alias"

      By default, those values are :

      • "Token Signing RSA public/private key pair" : "test"
      • "Token Signing ECDSA public/private key pair alias" : "ES512|test", "ES256|test", "ES384|test"

      What will do OpenAM is reading those two properties to identify the certificates it needs to list in the jwk_uri.

      There is two problems.

      Common reproduction step for the two sub problems:

      If you want to reproduce the issues, you would need to create 3 ES certificate.
      Here is how I did generated the certB, certC, certD:

      • Generate a cert A RS256 and add it to the keystore:
        keytool -genkey -keyalg RSA -alias certA -keystore keystore.jceks -storepass changeit -keypass changeit -storetype JCEKS -validity 360 -keysize 2048
        
      • Generate a cert B ES256 and add it to the keystore:
        forgerock@sandbox:~/openam13081/openam$
         openssl ecparam -genkey -name secp256r1 -noout -out ec256-key-pair.pem
        
        forgerock@sandbox:~/openam13081/openam$
        openssl req -new -key ec256-key-pair.pem -out server.csr
        You are about to be asked to enter information that will be incorporated
        into your certificate request.
        What you are about to enter is what is called a Distinguished Name or a DN.
        There are quite a few fields but you can leave some blank
        For some fields there will be a default value,
        If you enter '.', the field will be left blank.
        -----
        Country Name (2 letter code) [AU]:UK
        State or Province Name (full name) [Some-State]:
        Locality Name (eg, city) []:
        Organization Name (eg, company) [Internet Widgits Pty Ltd]:
        Organizational Unit Name (eg, section) []:
        Common Name (e.g. server FQDN or YOUR name) []:
        Email Address []:
        
        Please enter the following 'extra' attributes
        to be sent with your certificate request
        A challenge password []:
        An optional company name []:
        
        
        forgerock@sandbox:~/openam13081/openam$ openssl x509 -req -days 1365 -in server.csr -signkey ec256-key-pair.pem -out server.crt
        Signature ok
        subject=/C=UK/ST=Some-State/O=Internet Widgits Pty Ltd
        Getting Private key
        
        
        forgerock@sandbox:~/openam13081/openam$ openssl pkcs12  -inkey ec256-key-pair.pem -in server.crt -out store.p12 -export
        Enter Export Password:
        Verifying - Enter Export Password:
        
        
        forgerock@sandbox:~/openam13081/openam$ /usr/lib/jvm/java-7-oracle/jre/bin/keytool -v -importkeystore -srckeystore store.p12 -srcstoretype PKCS12 -destkeystore keystore.jceks -deststoretype JCEKS -destalias certC -srcalias 1
        Enter destination keystore password:  
        Enter source keystore password:  
        [Storing keystore.jceks]
        
      • Generate a cert C ES384 and add it to the keystore:
        forgerock@sandbox:~/openam13081/openam$
         openssl ecparam -genkey -name secp384r1 -noout -out ec384-key-pair.pem
        
        forgerock@sandbox:~/openam13081/openam$
        openssl req -new -key ec384-key-pair.pem -out server.csr
        You are about to be asked to enter information that will be incorporated
        into your certificate request.
        What you are about to enter is what is called a Distinguished Name or a DN.
        There are quite a few fields but you can leave some blank
        For some fields there will be a default value,
        If you enter '.', the field will be left blank.
        -----
        Country Name (2 letter code) [AU]:UK
        State or Province Name (full name) [Some-State]:
        Locality Name (eg, city) []:
        Organization Name (eg, company) [Internet Widgits Pty Ltd]:
        Organizational Unit Name (eg, section) []:
        Common Name (e.g. server FQDN or YOUR name) []:
        Email Address []:
        
        Please enter the following 'extra' attributes
        to be sent with your certificate request
        A challenge password []:
        An optional company name []:
        
        
        forgerock@sandbox:~/openam13081/openam$ openssl x509 -req -days 1365 -in server.csr -signkey ec384-key-pair.pem -out server.crt
        Signature ok
        subject=/C=UK/ST=Some-State/O=Internet Widgits Pty Ltd
        Getting Private key
        
        
        forgerock@sandbox:~/openam13081/openam$ openssl pkcs12  -inkey ec384-key-pair.pem -in server.crt -out store.p12 -export
        Enter Export Password:
        Verifying - Enter Export Password:
        
        
        forgerock@sandbox:~/openam13081/openam$ /usr/lib/jvm/java-7-oracle/jre/bin/keytool -v -importkeystore -srckeystore store.p12 -srcstoretype PKCS12 -destkeystore keystore.jceks -deststoretype JCEKS -destalias certC -srcalias 1
        Enter destination keystore password:  
        Enter source keystore password:  
        [Storing keystore.jceks]
        
      • Generate a cert D ES512 and add it to the keystore:
        forgerock@sandbox:~/openam13081/openam$
         openssl ecparam -genkey -name secp512r1 -noout -out ec512-key-pair.pem
        
        forgerock@sandbox:~/openam13081/openam$
        openssl req -new -key ec512-key-pair.pem -out server.csr
        You are about to be asked to enter information that will be incorporated
        into your certificate request.
        What you are about to enter is what is called a Distinguished Name or a DN.
        There are quite a few fields but you can leave some blank
        For some fields there will be a default value,
        If you enter '.', the field will be left blank.
        -----
        Country Name (2 letter code) [AU]:UK
        State or Province Name (full name) [Some-State]:
        Locality Name (eg, city) []:
        Organization Name (eg, company) [Internet Widgits Pty Ltd]:
        Organizational Unit Name (eg, section) []:
        Common Name (e.g. server FQDN or YOUR name) []:
        Email Address []:
        
        Please enter the following 'extra' attributes
        to be sent with your certificate request
        A challenge password []:
        An optional company name []:
        
        
        forgerock@sandbox:~/openam13081/openam$ openssl x509 -req -days 1365 -in server.csr -signkey ec512-key-pair.pem -out server.crt
        Signature ok
        subject=/C=UK/ST=Some-State/O=Internet Widgits Pty Ltd
        Getting Private key
        
        
        forgerock@sandbox:~/openam13081/openam$ openssl pkcs12  -inkey ec512-key-pair.pem -in server.crt -out store.p12 -export
        Enter Export Password:
        Verifying - Enter Export Password:
        
        
        forgerock@sandbox:~/openam13081/openam$ /usr/lib/jvm/java-7-oracle/jre/bin/keytool -v -importkeystore -srckeystore store.p12 -srcstoretype PKCS12 -destkeystore keystore.jceks -deststoretype JCEKS -destalias certC -srcalias 1
        Enter destination keystore password:  
        Enter source keystore password:  
        [Storing keystore.jceks]
        

      Problem 1)

      Description

      OpenAM will load properly the RSA certificate but for the others, it will still took the RSA certificate but for the ECDSA, it will create a weird association:

      It will load the RSA certificate instead of the ECDSA one but will mark it with the ECDSA.

      An example will be easier to understand:

      For

      • "Token Signing RSA public/private key pair" : "certA"
      • "Token Signing ECDSA public/private key pair alias" : "ES512|certB"

      Expected

      OpenAM will generate the JWKS :

      "certA" -> Key("CertA"),
      "CertB" -> Key("CertB")
      

      Actual behaviour

      "certA" -> Key("CertA"),
      "CertB" -> Key("CertA")
      

      Code investigation:

      here is code involved to get the list of Jwks

       public JsonValue getJWKSet() throws ServerException {
                          ...
                          Key key = getSigningKeyPair(realm, JwsAlgorithm.RS256).getPublic();
                          if (key != null && "RSA".equals(key.getAlgorithm())) {
                              jwks.add(createRSAJWK(getTokenSigningRSAKeyAlias(), (RSAPublicKey) key, KeyUse.SIG,
                                      JwsAlgorithm.RS256.name()));
                          } 
                         ...
      
                          Set<String> ecdsaAlgorithmAliases = getSetting(realm, TOKEN_SIGNING_ECDSA_KEYSTORE_ALIAS);
                          for (String algorithmAlias : ecdsaAlgorithmAliases) {
                            ...
                              String alias = aliasSplit[1];
                              key = getSigningKeyPair(realm, JwsAlgorithm.valueOf(aliasSplit[0].toUpperCase())).getPublic();
                             ...
                                  jwks.add(createECJWK(alias, (ECPublicKey) key, KeyUse.SIG));
                            ...
             
              return new JsonValue(Collections.singletonMap("keys", jwks));
          }
      

      So here, it reads the right properties and call getSigningKeyPair. Note that the alias is not send to the getSigningKeyPair function, but it should be possible to retrieve it by using the algorithm.

      public KeyPair getSigningKeyPair(String realm, JwsAlgorithm algorithm) throws SMSException, SSOException {
              if (JwsAlgorithmType.RSA.equals(algorithm.getAlgorithmType())) {
                  String alias = getStringSetting(realm, OAuth2Constants.OAuth2ProviderService.TOKEN_SIGNING_RSA_KEYSTORE_ALIAS);
                  return getServerKeyPair(realm, alias);
              } else if (JwsAlgorithmType.ECDSA.equals(algorithm.getAlgorithmType())) {
                  Set<String> algorithmAliases = getSetting(realm, OAuth2Constants.OAuth2ProviderService.TOKEN_SIGNING_RSA_KEYSTORE_ALIAS);
                  for (String algorithmAlias : algorithmAliases) {
                      if (StringUtils.isEmpty(algorithmAlias)) {
                          logger.warning("Empty signing key alias");
                          continue;
                      }
                      String[] aliasSplit = algorithmAlias.split("\\|");
                      if (aliasSplit.length != 2) {
                          logger.warning("Invalid signing key alias mapping: " + algorithmAlias);
                          continue;
                      }
                      return getServerKeyPair(realm, aliasSplit[1]);
                  }
              }
              return new KeyPair(null, null);
          }
      

      It's here that the error is made: we read the property :

                  Set<String> algorithmAliases = getSetting(realm, OAuth2Constants.OAuth2ProviderService.TOKEN_SIGNING_RSA_KEYSTORE_ALIAS);
      

      instead of

                  Set<String> algorithmAliases = getSetting(realm, OAuth2Constants.OAuth2ProviderService.TOKEN_SIGNING_ECDSA_KEYSTORE_ALIAS);
      

      Problem 2)

      Now, imagine we correct 1) : we actually read the right property. here is what happen:

      Description

      When reading the TOKEN_SIGNING_ECDSA_KEYSTORE_ALIAS, we get the list "ES512|test", "ES256|test", "ES384|test". For our example, we will need to use a different certificate by algorithm

      Note : I assume you can give a different certificate alias by algorithm, first because the format of this field suggest you can do that and then the doc info goes in that direction too:

      The list of public/private key pairs used for the elliptic curve algorithms (ES256/ES384/ES512). Add an entry to specify an alias for a specific elliptic curve algorithm, e.g. "ES256|es256Alias

      Each of the public/private key pairs will be retrieved from the keystore referenced by the property com.sun.identity.saml.xmlsig.keystore

      So lets say we have
      "ES512|certB", "ES256|certC", "ES384|certD"

      Expected behaviour:

      "certA" -> Key("CertA"),
      "CertB" -> Key("CertB")
      "CertC" -> Key("CertC")
      "CertD" -> Key("CertD")
      

      Current behaviour

      "certA" -> Key("CertA"),
      "CertB" -> Key("CertB")
      

      Code investigation:

      If you come back to the getSigningKeyPair function above:

          public KeyPair getSigningKeyPair(String realm, JwsAlgorithm algorithm) throws SMSException, SSOException {
      ...
      for (String algorithmAlias : algorithmAliases) {
                      if (StringUtils.isEmpty(algorithmAlias)) {
                          logger.warning("Empty signing key alias");
                          continue;
                      }
                      String[] aliasSplit = algorithmAlias.split("\\|");
                      if (aliasSplit.length != 2) {
                          logger.warning("Invalid signing key alias mapping: " + algorithmAlias);
                          continue;
                      }
                      return getServerKeyPair(realm, aliasSplit[1]);
                  }
      

      you will see that we don't check the algorithm, we took the first certificate alias of the list.

      Instead, we should have:

                      if (algorithm.equals(JwsAlgorithm.valueOf(aliasSplit[0].toUpperCase()))) {
                          return getServerKeyPair(realm, aliasSplit[1]);
                      }
      

      For QA:

      After correcting this issue, the output of http://openam.example.com:13081/openam/oauth2/connect/jwk_uri was for me:

      {
         "keys":[
            {
               "kty":"RSA",
               "kid":"8FGamW5IMfmpR3iGf6dLIsqu/DY=",
               "use":"sig",
               "alg":"RS256",
               "n":"AKzUPkntBWMBkOh9YslYlrS2mIH8RzzycgzSBONayXru7lO7iisDCrkgfFXdaPjF3l15hyZ2ebC7mQK_un1n3jqnNNlRz8flcjerNcn8GiEZmUlC9rGt4SZY05C8smk2j10TVwksjxr2v8ZrkF7KWKrflhKWhFdg_OvDF4-ilnYxrAC_mjGfih9hFYS177vaL_RtsdULGpx228vyiz7GL-1wENPSkc9ObIpuH_VrPlUrs_KtNLAgfUUy3uDIpcX_PK1qFKWabzjv1f3XMOW4yEmB4VxSzyhZ2TggVEM54Bi4c3YAXHeDHopUoHriOGONCqgaFBrUWHyZoPiF0CZuCEM",
               "e":"AQAB"
            },
            {
               "kty":"EC",
               "kid":"yId7wxvOSea0Ptk/t25hjo4VBiQ=",
               "use":"sig",
               "alg":"ES384",
               "x":"aVDaTLtRuNwxuWx2Y3ZxkebN1i4XC8Dy8jbq5iG6OqlpYQjLtxBmVrrVlZW3e5Ru",
               "y":"INajjZ13IUNYl3N-6mTvAAyYFnJ5r18ZbUUC2M3dDWsyMQbwBOn_rZaqFoJc7xXN",
               "crv":"P-384"
            },
            {
               "kty":"EC",
               "kid":"9gGrf3Fm73NmEMc+xaH1d5Z4tho=",
               "use":"sig",
               "alg":"ES512",
               "x":"ANn7zWWJDDWqHfPAxa3YQ8gE25HUcP-ZIxs-Pz3qg0UfI_HCboW78chn9jD0-GhwALIzQ7KXJHGNBp3tPLNEgsP2",
               "y":"Hl_mv93oiHnJnUvRTr3Bww3gHonO_70SJjDTDJu3k71VAMKpujIHKmAHasl3e6ty0LYqMZfn1CAczHGCfc-2sRk",
               "crv":"P-521"
            },
            {
               "kty":"EC",
               "kid":"7nGPFlN9gDz2kX8KBBfzDS+syhU=",
               "use":"sig",
               "alg":"ES256",
               "x":"fzPCgWO3whdtMHKxeHjgZwDo9-keq4eXj8OR3O2GyRg",
               "y":"AN1_JYcRFW5_2jI7pjq5siWh5FiIIyjSMxq_hrGLdwfL",
               "crv":"P-256"
            }
         ]
      }
      

      You can see that you get 4 JWK, one by algorithm. You can also check the alg attributes: you should have RS256, ES256, ES384, ES512.

        Attachments

          Issue Links

            Activity

              People

              • Assignee:
                quentin.castel Quentin CASTEL [X] (Inactive)
                Reporter:
                quentin.castel Quentin CASTEL [X] (Inactive)
              • Votes:
                0 Vote for this issue
                Watchers:
                4 Start watching this issue

                Dates

                • Created:
                  Updated:
                  Resolved: