/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.cxf.rs.security.jose.jaxrs; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import javax.annotation.Priority; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.ext.WriterInterceptor; import javax.ws.rs.ext.WriterInterceptorContext; import org.apache.cxf.common.util.Base64UrlOutputStream; import org.apache.cxf.common.util.Base64UrlUtility; import org.apache.cxf.common.util.StringUtils; import org.apache.cxf.io.CachedOutputStream; import org.apache.cxf.jaxrs.json.basic.JsonMapObjectReaderWriter; import org.apache.cxf.jaxrs.utils.JAXRSUtils; import org.apache.cxf.rs.security.jose.common.JoseConstants; import org.apache.cxf.rs.security.jose.common.JoseHeaders; import org.apache.cxf.rs.security.jose.common.JoseUtils; import org.apache.cxf.rs.security.jose.jws.JwsCompactProducer; import org.apache.cxf.rs.security.jose.jws.JwsHeaders; import org.apache.cxf.rs.security.jose.jws.JwsOutputStream; import org.apache.cxf.rs.security.jose.jws.JwsSignature; import org.apache.cxf.rs.security.jose.jws.JwsSignatureProvider; @Priority(Priorities.JWS_WRITE_PRIORITY) public class JwsWriterInterceptor extends AbstractJwsWriterProvider implements WriterInterceptor { private static final Set<String> DEFAULT_PROTECTED_HTTP_HEADERS = new HashSet<String>(Arrays.asList(HttpHeaders.CONTENT_TYPE, HttpHeaders.ACCEPT)); private Set<String> protectedHttpHeaders = DEFAULT_PROTECTED_HTTP_HEADERS; private boolean protectHttpHeaders; private boolean contentTypeRequired = true; private boolean useJwsOutputStream; private boolean encodePayload = true; private JsonMapObjectReaderWriter writer = new JsonMapObjectReaderWriter(); @Override public void aroundWriteTo(WriterInterceptorContext ctx) throws IOException, WebApplicationException { if (ctx.getEntity() == null) { ctx.proceed(); return; } JwsHeaders headers = new JwsHeaders(); JwsSignatureProvider sigProvider = getInitializedSigProvider(headers); setContentTypeIfNeeded(headers, ctx); if (!encodePayload) { headers.setPayloadEncodingStatus(false); } protectHttpHeadersIfNeeded(ctx, headers); OutputStream actualOs = ctx.getOutputStream(); if (useJwsOutputStream) { JwsSignature jwsSignature = sigProvider.createJwsSignature(headers); JoseUtils.traceHeaders(headers); JwsOutputStream jwsStream = new JwsOutputStream(actualOs, jwsSignature, true); byte[] headerBytes = StringUtils.toBytesUTF8(writer.toJson(headers)); Base64UrlUtility.encodeAndStream(headerBytes, 0, headerBytes.length, jwsStream); jwsStream.write(new byte[]{'.'}); Base64UrlOutputStream base64Stream = null; if (encodePayload) { base64Stream = new Base64UrlOutputStream(jwsStream); ctx.setOutputStream(base64Stream); } else { ctx.setOutputStream(jwsStream); } ctx.proceed(); setJoseMediaType(ctx); if (base64Stream != null) { base64Stream.flush(); } jwsStream.flush(); } else { CachedOutputStream cos = new CachedOutputStream(); ctx.setOutputStream(cos); ctx.proceed(); JwsCompactProducer p = new JwsCompactProducer(headers, new String(cos.getBytes(), StandardCharsets.UTF_8)); setJoseMediaType(ctx); writeJws(p, sigProvider, actualOs); } } public void setContentTypeRequired(boolean contentTypeRequired) { this.contentTypeRequired = contentTypeRequired; } public void setUseJwsOutputStream(boolean useJwsOutputStream) { this.useJwsOutputStream = useJwsOutputStream; } private void setContentTypeIfNeeded(JoseHeaders headers, WriterInterceptorContext ctx) { if (contentTypeRequired) { MediaType mt = ctx.getMediaType(); if (mt != null && !JAXRSUtils.mediaTypeToString(mt).equals(JoseConstants.MEDIA_TYPE_JOSE)) { if ("application".equals(mt.getType())) { headers.setContentType(mt.getSubtype()); } else { headers.setContentType(JAXRSUtils.mediaTypeToString(mt)); } } } } private void setJoseMediaType(WriterInterceptorContext ctx) { MediaType joseMediaType = JAXRSUtils.toMediaType(JoseConstants.MEDIA_TYPE_JOSE); ctx.setMediaType(joseMediaType); } public void setEncodePayload(boolean encodePayload) { this.encodePayload = encodePayload; } protected void protectHttpHeadersIfNeeded(WriterInterceptorContext ctx, JwsHeaders jwsHeaders) { if (protectHttpHeaders) { JoseJaxrsUtils.protectHttpHeaders(ctx.getHeaders(), jwsHeaders, protectedHttpHeaders); } } public void setProtectHttpHeaders(boolean protectHttpHeaders) { this.protectHttpHeaders = protectHttpHeaders; } public void setProtectedHttpHeaders(Set<String> protectedHttpHeaders) { this.protectedHttpHeaders = protectedHttpHeaders; } }