From ee8b7536e1fcd1fc1e59899d2f225abc0cbf40e2 Mon Sep 17 00:00:00 2001 From: MaxKey Date: Fri, 18 Feb 2022 15:21:29 +0800 Subject: [PATCH] Metadata PEM support --- .../main/java/org/maxkey/crypto/RSAUtils.java | 39 ++++++++++++++ .../crypto/jose/keystore/JWKSetKeyStore.java | 54 ++++++++++++++++++- .../token/endpoint/JwtAuthorizeEndpoint.java | 49 ++++++++++------- .../endpoint/AuthorizationEndpoint.java | 45 ++++++++++------ .../endpoint/SamlMetadataEndpoint.java | 3 +- 5 files changed, 152 insertions(+), 38 deletions(-) diff --git a/maxkey-common/src/main/java/org/maxkey/crypto/RSAUtils.java b/maxkey-common/src/main/java/org/maxkey/crypto/RSAUtils.java index d143f4894..d5af704d6 100644 --- a/maxkey-common/src/main/java/org/maxkey/crypto/RSAUtils.java +++ b/maxkey-common/src/main/java/org/maxkey/crypto/RSAUtils.java @@ -25,6 +25,7 @@ import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; import java.util.HashMap; import java.util.Map; @@ -38,6 +39,8 @@ public final class RSAUtils { public static final String PUBLIC_KEY = "RSAPublicKey"; public static final String PRIVATE_KEY = "RSAPrivateKey"; + + public static final int BASE64ARRAY_SIZE = 64; public static Map genKeyPair() throws Exception { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORTHM); @@ -173,5 +176,41 @@ public final class RSAUtils { return cipher.doFinal(data); } + + public static String getPublicKeyPEM(byte[] encoded) { + StringBuffer base64String = + new StringBuffer(""); + base64String.append("-----BEGIN PUBLIC KEY-----").append("\n"); + base64String.append(getBase64PEM(encoded)); + base64String.append("-----END PUBLIC KEY-------").append("\n"); + return base64String.toString(); + } + + public static String getPrivateKeyPEM(byte[] encoded) { + StringBuffer base64String = + new StringBuffer(""); + base64String.append("-----BEGIN RSA PRIVATE KEY-----").append("\n"); + base64String.append(getBase64PEM(encoded)); + base64String.append("-----END RSA PRIVATE KEY-------").append("\n"); + return base64String.toString(); + } + + public static String getBase64PEM(byte[] encoded) { + String base64String = Base64.getEncoder().encodeToString(encoded); + StringBuffer base64ArrayString = new StringBuffer(""); + int startPosition = 0; + int endPosition = BASE64ARRAY_SIZE; + while(endPosition < base64String.length()) { + base64ArrayString.append(base64String.substring(startPosition, endPosition)).append("\n"); + startPosition = endPosition; + endPosition = endPosition + BASE64ARRAY_SIZE; + } + if(startPosition < base64String.length()) { + base64ArrayString.append(base64String.substring(startPosition)).append("\n"); + } + + return base64ArrayString.toString(); + } + } diff --git a/maxkey-common/src/main/java/org/maxkey/crypto/jose/keystore/JWKSetKeyStore.java b/maxkey-common/src/main/java/org/maxkey/crypto/jose/keystore/JWKSetKeyStore.java index 3f09f33c5..82dc0c53e 100644 --- a/maxkey-common/src/main/java/org/maxkey/crypto/jose/keystore/JWKSetKeyStore.java +++ b/maxkey-common/src/main/java/org/maxkey/crypto/jose/keystore/JWKSetKeyStore.java @@ -18,12 +18,20 @@ package org.maxkey.crypto.jose.keystore; import com.google.common.base.Charsets; import com.google.common.io.CharStreams; +import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.RSAKey; + import java.io.IOException; import java.io.InputStreamReader; +import java.security.PublicKey; import java.text.ParseException; import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.maxkey.crypto.RSAUtils; +import org.maxkey.pretty.PrettyFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.Resource; @@ -131,5 +139,49 @@ public class JWKSetKeyStore { } return jwkSet.getKeys(); } - + + public String toString(String mediaType){ + StringBuffer metaDataString = new StringBuffer(""); + if(StringUtils.isNotBlank(mediaType) && mediaType.equalsIgnoreCase("XML")) { + metaDataString.append("").append("\n"); + for(JWK jwk : jwkSet.getKeys()) { + RSAKey rsaKey = jwk.toRSAKey(); + PublicKey publicKey; + try { + publicKey = rsaKey.toPublicKey(); + metaDataString.append("").append("\n"); + metaDataString.append(RSAUtils.getPublicKeyPEM(publicKey.getEncoded())); + metaDataString.append("").append("\n"); + //keyID + metaDataString.append(""); + metaDataString.append(rsaKey.getAlgorithm()); + metaDataString.append("").append("\n"); + + metaDataString.append(""); + metaDataString.append(rsaKey.getKeyID()); + metaDataString.append("").append("\n"); + + metaDataString.append(""); + metaDataString.append(rsaKey.getKeyType()); + metaDataString.append("").append("\n"); + + metaDataString.append(""); + metaDataString.append(publicKey.getFormat()); + metaDataString.append(""); + + metaDataString.append(""); + metaDataString.append(rsaKey.getPublicExponent()); + metaDataString.append("").append("\n"); + } catch (JOSEException e) { + _logger.error("JOSEException ", mediaType); + } + } + metaDataString.append(""); + }else { + metaDataString.append(PrettyFactory.getJsonPretty().format( + jwkSet.toPublicJWKSet().toString())); + } + + return metaDataString.toString(); + } } diff --git a/maxkey-protocols/maxkey-protocol-jwt/src/main/java/org/maxkey/authz/token/endpoint/JwtAuthorizeEndpoint.java b/maxkey-protocols/maxkey-protocol-jwt/src/main/java/org/maxkey/authz/token/endpoint/JwtAuthorizeEndpoint.java index 5b7f8985a..925f7e741 100644 --- a/maxkey-protocols/maxkey-protocol-jwt/src/main/java/org/maxkey/authz/token/endpoint/JwtAuthorizeEndpoint.java +++ b/maxkey-protocols/maxkey-protocol-jwt/src/main/java/org/maxkey/authz/token/endpoint/JwtAuthorizeEndpoint.java @@ -21,7 +21,6 @@ package org.maxkey.authz.token.endpoint; import java.lang.reflect.InvocationTargetException; - import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -34,12 +33,13 @@ import org.maxkey.authz.endpoint.adapter.AbstractAuthorizeAdapter; import org.maxkey.authz.jwt.endpoint.adapter.JwtAdapter; import org.maxkey.configuration.ApplicationConfig; import org.maxkey.constants.ConstsBoolean; +import org.maxkey.constants.ContentType; import org.maxkey.crypto.jose.keystore.JWKSetKeyStore; import org.maxkey.entity.apps.Apps; import org.maxkey.entity.apps.AppsJwtDetails; import org.maxkey.persistence.service.AppsJwtDetailsService; -import org.maxkey.pretty.PrettyFactory; import org.maxkey.util.Instance; +import org.maxkey.web.HttpRequestAdapter; import org.maxkey.web.WebConstants; import org.maxkey.web.WebContext; import org.slf4j.Logger; @@ -138,27 +138,38 @@ public class JwtAuthorizeEndpoint extends AuthorizeBaseEndpoint{ } @Operation(summary = "JWT JWK元数据接口", description = "参数mxk_metadata_APPID",method="GET") - @RequestMapping(value = "/metadata/jwt/{appid}.json",produces = "application/json", method={RequestMethod.POST, RequestMethod.GET}) + @RequestMapping( + value = "/metadata/jwt/" + WebConstants.MXK_METADATA_PREFIX + "{appid}.{mediaType}", + method={RequestMethod.POST, RequestMethod.GET}) @ResponseBody public String metadata(HttpServletRequest request, - HttpServletResponse response, @PathVariable("appid") String appId) { - appId = appId.substring(WebConstants.MXK_METADATA_PREFIX.length(), appId.length()); + HttpServletResponse response, + @PathVariable("appid") String appId, + @PathVariable("mediaType") String mediaType) { AppsJwtDetails jwtDetails = jwtDetailsService.getAppDetails(appId); - String jwkSetString = ""; - if(!jwtDetails.getSignature().equalsIgnoreCase("none")) { - jwkSetString = jwtDetails.getSignatureKey(); - } - if(!jwtDetails.getAlgorithm().equalsIgnoreCase("none")) { - if(StringUtils.isBlank(jwkSetString)) { - jwkSetString = jwtDetails.getAlgorithmKey(); - }else { - jwkSetString = jwkSetString + "," +jwtDetails.getAlgorithmKey(); + if(jwtDetails != null) { + String jwkSetString = ""; + if(!jwtDetails.getSignature().equalsIgnoreCase("none")) { + jwkSetString = jwtDetails.getSignatureKey(); } + if(!jwtDetails.getAlgorithm().equalsIgnoreCase("none")) { + if(StringUtils.isBlank(jwkSetString)) { + jwkSetString = jwtDetails.getAlgorithmKey(); + }else { + jwkSetString = jwkSetString + "," +jwtDetails.getAlgorithmKey(); + } + } + + JWKSetKeyStore jwkSetKeyStore = new JWKSetKeyStore("{\"keys\": [" + jwkSetString + "]}"); + if(StringUtils.isNotBlank(mediaType) + && mediaType.equalsIgnoreCase(HttpRequestAdapter.MediaType.XML)) { + response.setContentType(ContentType.APPLICATION_XML_UTF8); + }else { + response.setContentType(ContentType.APPLICATION_JSON_UTF8); + } + return jwkSetKeyStore.toString(mediaType); + } - - JWKSetKeyStore jwkSetKeyStore = new JWKSetKeyStore("{\"keys\": [" + jwkSetString + "]}"); - - return PrettyFactory.getJsonPretty().format( - jwkSetKeyStore.getJwkSet().toPublicJWKSet().toString()); + return appId + " not exist."; } } diff --git a/maxkey-protocols/maxkey-protocol-oauth-2.0/src/main/java/org/maxkey/authz/oauth2/provider/endpoint/AuthorizationEndpoint.java b/maxkey-protocols/maxkey-protocol-oauth-2.0/src/main/java/org/maxkey/authz/oauth2/provider/endpoint/AuthorizationEndpoint.java index 9485778d9..fb4dc2517 100644 --- a/maxkey-protocols/maxkey-protocol-oauth-2.0/src/main/java/org/maxkey/authz/oauth2/provider/endpoint/AuthorizationEndpoint.java +++ b/maxkey-protocols/maxkey-protocol-oauth-2.0/src/main/java/org/maxkey/authz/oauth2/provider/endpoint/AuthorizationEndpoint.java @@ -42,11 +42,12 @@ import org.maxkey.authz.oauth2.provider.approval.UserApprovalHandler; import org.maxkey.authz.oauth2.provider.code.AuthorizationCodeServices; import org.maxkey.authz.oauth2.provider.implicit.ImplicitTokenRequest; import org.maxkey.authz.oauth2.provider.request.DefaultOAuth2RequestValidator; +import org.maxkey.constants.ContentType; import org.maxkey.crypto.jose.keystore.JWKSetKeyStore; import org.maxkey.util.HttpEncoder; import org.maxkey.entity.apps.Apps; import org.maxkey.entity.apps.oauth2.provider.ClientDetails; -import org.maxkey.pretty.PrettyFactory; +import org.maxkey.web.HttpRequestAdapter; import org.maxkey.web.WebConstants; import org.maxkey.web.WebContext; import org.slf4j.Logger; @@ -291,27 +292,39 @@ public class AuthorizationEndpoint extends AbstractEndpoint { } @Operation(summary = "OAuth JWk 元数据接口", description = "参数mxk_metadata_APPID",method="GET") - @RequestMapping(value = "/metadata/oauth/v20/{appid}.json",produces = "application/json", method={RequestMethod.POST, RequestMethod.GET}) + @RequestMapping( + value = "/metadata/oauth/v20/" + WebConstants.MXK_METADATA_PREFIX + "{appid}.{mediaType}", + method={RequestMethod.POST, RequestMethod.GET}) @ResponseBody public String metadata(HttpServletRequest request, - HttpServletResponse response, @PathVariable("appid") String appId) { - appId = appId.substring(WebConstants.MXK_METADATA_PREFIX.length(), appId.length()); + HttpServletResponse response, + @PathVariable("appid") String appId, + @PathVariable("mediaType") String mediaType) { ClientDetails clientDetails = getClientDetailsService().loadClientByClientId(appId,true); - String jwkSetString = ""; - if(!clientDetails.getSignature().equalsIgnoreCase("none")) { - jwkSetString = clientDetails.getSignatureKey(); - } - if(!clientDetails.getAlgorithm().equalsIgnoreCase("none")) { - if(!StringUtils.hasText(jwkSetString)) { - jwkSetString = clientDetails.getAlgorithmKey(); - }else { - jwkSetString = jwkSetString + "," +clientDetails.getAlgorithmKey(); + if(clientDetails != null) { + String jwkSetString = ""; + if(!clientDetails.getSignature().equalsIgnoreCase("none")) { + jwkSetString = clientDetails.getSignatureKey(); } + if(!clientDetails.getAlgorithm().equalsIgnoreCase("none")) { + if(!StringUtils.hasText(jwkSetString)) { + jwkSetString = clientDetails.getAlgorithmKey(); + }else { + jwkSetString = jwkSetString + "," +clientDetails.getAlgorithmKey(); + } + } + JWKSetKeyStore jwkSetKeyStore = new JWKSetKeyStore("{\"keys\": [" + jwkSetString + "]}"); + + if(StringUtils.hasText(mediaType) + && mediaType.equalsIgnoreCase(HttpRequestAdapter.MediaType.XML)) { + response.setContentType(ContentType.APPLICATION_XML_UTF8); + }else { + response.setContentType(ContentType.APPLICATION_JSON_UTF8); + } + return jwkSetKeyStore.toString(mediaType); } - JWKSetKeyStore jwkSetKeyStore = new JWKSetKeyStore("{\"keys\": [" + jwkSetString + "]}"); - return PrettyFactory.getJsonPretty().format( - jwkSetKeyStore.getJwkSet().toPublicJWKSet().toString()); + return appId + " not exist."; } // We need explicit approval from the user. diff --git a/maxkey-protocols/maxkey-protocol-saml-2.0/src/main/java/org/maxkey/authz/saml20/metadata/endpoint/SamlMetadataEndpoint.java b/maxkey-protocols/maxkey-protocol-saml-2.0/src/main/java/org/maxkey/authz/saml20/metadata/endpoint/SamlMetadataEndpoint.java index 6abe4f9e2..f00f7174d 100644 --- a/maxkey-protocols/maxkey-protocol-saml-2.0/src/main/java/org/maxkey/authz/saml20/metadata/endpoint/SamlMetadataEndpoint.java +++ b/maxkey-protocols/maxkey-protocol-saml-2.0/src/main/java/org/maxkey/authz/saml20/metadata/endpoint/SamlMetadataEndpoint.java @@ -76,12 +76,11 @@ public class SamlMetadataEndpoint { private Credential signingCredential; @Operation(summary = "SAML 2.0 元数据接口", description = "参数mxk_metadata_APPID",method="GET") - @RequestMapping(value = "/{appid}.xml",produces = "application/xml", method={RequestMethod.POST, RequestMethod.GET}) + @RequestMapping(value = "/" + WebConstants.MXK_METADATA_PREFIX + "{appid}.xml",produces = "application/xml", method={RequestMethod.POST, RequestMethod.GET}) @ResponseBody public String metadata(HttpServletRequest request, HttpServletResponse response, @PathVariable("appid") String appId) { response.setContentType(ContentType.APPLICATION_XML_UTF8); - appId = appId.substring(WebConstants.MXK_METADATA_PREFIX.length(), appId.length()); if(signingCredential == null){ TrustResolver trustResolver = new TrustResolver(); CredentialResolver credentialResolver=(CredentialResolver)trustResolver.buildKeyStoreCredentialResolver(