You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by se...@apache.org on 2015/02/10 14:34:48 UTC

cxf git commit: [CXF-6085] Prototyping the actual JweJson producer code, using the patch from Daniel Torkian as the input

Repository: cxf
Updated Branches:
  refs/heads/master 7944123fb -> 8891549dc


[CXF-6085] Prototyping the actual JweJson producer code, using the patch from Daniel Torkian as the input


Project: http://git-wip-us.apache.org/repos/asf/cxf/repo
Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/8891549d
Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/8891549d
Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/8891549d

Branch: refs/heads/master
Commit: 8891549dc9f6a10e879430d18836459a17a94100
Parents: 7944123
Author: Sergey Beryozkin <sb...@talend.com>
Authored: Tue Feb 10 13:34:31 2015 +0000
Committer: Sergey Beryozkin <sb...@talend.com>
Committed: Tue Feb 10 13:34:31 2015 +0000

----------------------------------------------------------------------
 .../jose/jaxrs/JweWriterInterceptor.java        |   4 +-
 .../jose/jwe/AbstractJweEncryption.java         |  39 +++--
 .../security/jose/jwe/JweEncryptionInput.java   |  60 +++++++
 .../jose/jwe/JweEncryptionProvider.java         |   8 +-
 .../jose/jwe/JweJsonEncryptionEntry.java        |  56 +++++++
 .../rs/security/jose/jwe/JweJsonProducer.java   | 168 +++++++++++++++++++
 6 files changed, 314 insertions(+), 21 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cxf/blob/8891549d/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweWriterInterceptor.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweWriterInterceptor.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweWriterInterceptor.java
index ca4f5c9..7291edd 100644
--- a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweWriterInterceptor.java
+++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweWriterInterceptor.java
@@ -35,6 +35,7 @@ import org.apache.cxf.io.CachedOutputStream;
 import org.apache.cxf.jaxrs.utils.JAXRSUtils;
 import org.apache.cxf.rs.security.jose.JoseConstants;
 import org.apache.cxf.rs.security.jose.jwe.JweCompactProducer;
+import org.apache.cxf.rs.security.jose.jwe.JweEncryptionInput;
 import org.apache.cxf.rs.security.jose.jwe.JweEncryptionProvider;
 import org.apache.cxf.rs.security.jose.jwe.JweEncryptionState;
 import org.apache.cxf.rs.security.jose.jwe.JweHeaders;
@@ -70,7 +71,8 @@ public class JweWriterInterceptor implements WriterInterceptor {
         }
         
         if (useJweOutputStream) {
-            JweEncryptionState encryption = theEncryptionProvider.createJweEncryptionState(jweHeaders, null);
+            JweEncryptionState encryption = 
+                theEncryptionProvider.createJweEncryptionState(new JweEncryptionInput(jweHeaders));
             try {
                 JweCompactProducer.startJweContent(actualOs,
                                                    encryption.getHeaders(), 

http://git-wip-us.apache.org/repos/asf/cxf/blob/8891549d/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/AbstractJweEncryption.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/AbstractJweEncryption.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/AbstractJweEncryption.java
index 9103ecf..e0d7cf6 100644
--- a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/AbstractJweEncryption.java
+++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/AbstractJweEncryption.java
@@ -107,8 +107,8 @@ public abstract class AbstractJweEncryption implements JweEncryptionProvider {
         return writer;
     }
     @Override
-    public JweEncryptionState createJweEncryptionState(JweHeaders jweHeaders, byte[] aad) {
-        JweEncryptionInternal state = getInternalState(jweHeaders, aad);
+    public JweEncryptionState createJweEncryptionState(JweEncryptionInput jweInput) {
+        JweEncryptionInternal state = getInternalState(jweInput.getJweHeaders(), jweInput);
         Cipher c = CryptoUtils.initCipher(createCekSecretKey(state), state.keyProps, 
                                           Cipher.ENCRYPT_MODE);
         return new JweEncryptionState(c, 
@@ -130,7 +130,7 @@ public abstract class AbstractJweEncryption implements JweEncryptionProvider {
         return theCek;
     }
     
-    private JweEncryptionInternal getInternalState(JweHeaders jweHeaders, byte[] aad) {
+    private JweEncryptionInternal getInternalState(JweHeaders jweInHeaders, JweEncryptionInput jweInput) {
         JweHeaders theHeaders = new JweHeaders();
         if (getKeyAlgorithm() != null) {
             theHeaders.setKeyEncryptionAlgorithm(getKeyAlgorithm());
@@ -138,33 +138,35 @@ public abstract class AbstractJweEncryption implements JweEncryptionProvider {
         theHeaders.setContentEncryptionAlgorithm(getContentAlgorithm());
         
         JweHeaders protectedHeaders = null;
-        if (jweHeaders != null) {
-            if (jweHeaders.getKeyEncryptionAlgorithm() != null 
+        if (jweInHeaders != null) {
+            if (jweInHeaders.getKeyEncryptionAlgorithm() != null 
                 && (getKeyAlgorithm() == null 
-                    || !getKeyAlgorithm().equals(jweHeaders.getKeyEncryptionAlgorithm()))
-                || jweHeaders.getAlgorithm() != null 
-                    && !getContentAlgorithm().equals(jweHeaders.getAlgorithm())) {
+                    || !getKeyAlgorithm().equals(jweInHeaders.getKeyEncryptionAlgorithm()))
+                || jweInHeaders.getAlgorithm() != null 
+                    && !getContentAlgorithm().equals(jweInHeaders.getAlgorithm())) {
                 throw new SecurityException();
             }
-            theHeaders.asMap().putAll(jweHeaders.asMap());
-            if (jweHeaders.getProtectedHeaders() != null 
-                && !jweHeaders.asMap().entrySet().containsAll(theHeaders.asMap().entrySet())) {
-                jweHeaders.getProtectedHeaders().asMap().putAll(theHeaders.asMap());
+            theHeaders.asMap().putAll(jweInHeaders.asMap());
+            if (jweInHeaders.getProtectedHeaders() != null 
+                && !jweInHeaders.asMap().entrySet().containsAll(theHeaders.asMap().entrySet())) {
+                jweInHeaders.getProtectedHeaders().asMap().putAll(theHeaders.asMap());
             }
-            protectedHeaders = jweHeaders.getProtectedHeaders() != null 
-                ? jweHeaders.getProtectedHeaders() : theHeaders;
+            protectedHeaders = jweInHeaders.getProtectedHeaders() != null 
+                ? jweInHeaders.getProtectedHeaders() : theHeaders;
         } else {
             protectedHeaders = theHeaders;
         }
         
         
         
-        byte[] theCek = getContentEncryptionKey(theHeaders);
+        byte[] theCek = jweInput != null && jweInput.getCek() != null 
+            ? jweInput.getCek() : getContentEncryptionKey(theHeaders);
         String contentEncryptionAlgoJavaName = Algorithm.toJavaName(getContentEncryptionAlgoJwt());
         KeyProperties keyProps = new KeyProperties(contentEncryptionAlgoJavaName);
         keyProps.setCompressionSupported(compressionRequired(theHeaders));
         
-        byte[] theIv = getContentEncryptionAlgorithm().getInitVector();
+        byte[] theIv = jweInput != null && jweInput.getIv() != null  
+            ? jweInput.getIv() : getContentEncryptionAlgorithm().getInitVector();
         AlgorithmParameterSpec specParams = getAlgorithmParameterSpec(theIv);
         keyProps.setAlgoSpec(specParams);
         byte[] jweContentEncryptionKey = 
@@ -173,7 +175,8 @@ public abstract class AbstractJweEncryption implements JweEncryptionProvider {
         
         String protectedHeadersJson = writer.headersToJson(protectedHeaders);
         
-        byte[] additionalEncryptionParam = getAAD(protectedHeadersJson, aad);
+        byte[] additionalEncryptionParam = getAAD(protectedHeadersJson, 
+                                                  jweInput == null ? null : jweInput.getAad());
         keyProps.setAdditionalData(additionalEncryptionParam);
         
         JweEncryptionInternal state = new JweEncryptionInternal();
@@ -183,7 +186,7 @@ public abstract class AbstractJweEncryption implements JweEncryptionProvider {
         state.secretKey = theCek; 
         state.theIv = theIv;
         state.protectedHeadersJson = protectedHeadersJson;
-        state.aad = aad;
+        state.aad = jweInput != null ? jweInput.getAad() : null;
         return state;
     }
     private boolean compressionRequired(JweHeaders theHeaders) {

http://git-wip-us.apache.org/repos/asf/cxf/blob/8891549d/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweEncryptionInput.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweEncryptionInput.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweEncryptionInput.java
new file mode 100644
index 0000000..cb07be3
--- /dev/null
+++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweEncryptionInput.java
@@ -0,0 +1,60 @@
+/**
+ * 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.jwe;
+
+public class JweEncryptionInput {
+    private JweHeaders jweHeaders;
+    private byte[] cek;
+    private byte[] iv;
+    private byte[] aad;
+    public JweEncryptionInput(JweHeaders jweHeaders) {
+        this.jweHeaders = jweHeaders;
+    }
+    public JweEncryptionInput(JweHeaders jweHeaders,
+                              byte[] cek,
+                              byte[] iv) {
+        this(jweHeaders, cek, iv, null);
+        
+    }
+    public JweEncryptionInput(JweHeaders jweHeaders,
+                              byte[] aad) {
+        this(jweHeaders, null, null, aad);
+    }
+    public JweEncryptionInput(JweHeaders jweHeaders,
+                              byte[] cek,
+                              byte[] iv,
+                              byte[] aad) {
+        this(jweHeaders);
+        this.cek = cek;
+        this.iv = iv;
+        this.aad = aad;
+    }
+    public JweHeaders getJweHeaders() {
+        return jweHeaders;
+    }
+    public byte[] getCek() {
+        return cek;
+    }
+    public byte[] getIv() {
+        return iv;
+    }
+    public byte[] getAad() {
+        return aad;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cxf/blob/8891549d/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweEncryptionProvider.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweEncryptionProvider.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweEncryptionProvider.java
index df4a0d9..25b931a 100644
--- a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweEncryptionProvider.java
+++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweEncryptionProvider.java
@@ -21,9 +21,13 @@ package org.apache.cxf.rs.security.jose.jwe;
 
 
 public interface JweEncryptionProvider extends JweKeyProperties {
+    /**
+     * JWE compact encryption
+     */
     String encrypt(byte[] jweContent, JweHeaders jweHeaders);
     /**
-     * Prepare JWE state (optional operation)
+     * Prepare JWE state for completing either
+     * JWE compact or JSON encryption 
      */
-    JweEncryptionState createJweEncryptionState(JweHeaders jweHeaders, byte[] aad);
+    JweEncryptionState createJweEncryptionState(JweEncryptionInput jweInput);
 }

http://git-wip-us.apache.org/repos/asf/cxf/blob/8891549d/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweJsonEncryptionEntry.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweJsonEncryptionEntry.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweJsonEncryptionEntry.java
new file mode 100644
index 0000000..cdba53e
--- /dev/null
+++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweJsonEncryptionEntry.java
@@ -0,0 +1,56 @@
+/**
+ * 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.jwe;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.cxf.jaxrs.provider.json.JsonMapObjectReaderWriter;
+
+public class JweJsonEncryptionEntry {
+    private JweHeaders unprotectedHeader;
+    private String encodedEncryptedKey;
+    public JweJsonEncryptionEntry(String encodedEncryptedKey) {
+        this(null, encodedEncryptedKey);
+    }
+    public JweJsonEncryptionEntry(JweHeaders unprotectedHeader, String encodedEncryptedKey) {
+        this.unprotectedHeader = unprotectedHeader;
+        this.encodedEncryptedKey = encodedEncryptedKey;
+    }
+    public JweHeaders getUnprotectedHeader() {
+        return unprotectedHeader;
+    }
+    public String getEncodedEncryptedKey() {
+        return encodedEncryptedKey;
+    }
+    public String toJson() {
+        JsonMapObjectReaderWriter jsonWriter = new JsonMapObjectReaderWriter();
+        Map<String, Object> recipientsEntry = new LinkedHashMap<String, Object>();
+        if (unprotectedHeader != null) {
+            recipientsEntry.put("header", this.unprotectedHeader);
+        }
+        if (encodedEncryptedKey != null) {
+            recipientsEntry.put("encrypted_key", this.encodedEncryptedKey);
+        }
+        return jsonWriter.toJson(recipientsEntry);
+    }
+    public String toString() {
+        return toJson();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cxf/blob/8891549d/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweJsonProducer.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweJsonProducer.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweJsonProducer.java
new file mode 100644
index 0000000..8879e24
--- /dev/null
+++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweJsonProducer.java
@@ -0,0 +1,168 @@
+/**
+ * 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.jwe;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.cxf.common.util.Base64UrlUtility;
+import org.apache.cxf.common.util.crypto.CryptoUtils;
+import org.apache.cxf.rs.security.jose.JoseHeadersReaderWriter;
+
+public class JweJsonProducer {
+    private JoseHeadersReaderWriter writer = new JoseHeadersReaderWriter();
+    private JweHeaders protectedHeader;
+    private JweHeaders unprotectedHeader;
+    private byte[] content;
+    private byte[] aad;
+    public JweJsonProducer(JweHeaders protectedHeader, byte[] content) {
+        this.protectedHeader = protectedHeader;
+        this.content = content;    
+    }
+    public JweJsonProducer(JweHeaders protectedHeader, byte[] content, byte[] aad) {
+        this(protectedHeader, content);
+        this.aad = aad;
+    }
+    public JweJsonProducer(JweHeaders protectedHeader, JweHeaders unprotectedHeader, 
+                           byte[] content, byte[] aad) {
+        this(protectedHeader, content, aad);
+        this.unprotectedHeader = unprotectedHeader;
+    }
+    
+    public String encryptWith(List<JweEncryptionProvider> encryptors) {
+        return encryptWith(encryptors, null);
+    }
+    public String encryptWith(List<JweEncryptionProvider> encryptors, 
+                              List<JweHeaders> recepientUnprotected) {
+        checkAndGetContentAlgorithm(encryptors);
+        if (recepientUnprotected != null 
+            && recepientUnprotected.size() != encryptors.size()) {
+            throw new IllegalArgumentException();
+        }
+        //TODO: determine the actual cek and iv length based on the algo
+        byte[] cek = CryptoUtils.generateSecureRandomBytes(32);
+        byte[] iv = CryptoUtils.generateSecureRandomBytes(16);
+        JweHeaders unionHeaders = new JweHeaders();
+        unionHeaders.setProtectedHeaders(protectedHeader);
+        if (protectedHeader != null) {
+            unionHeaders.asMap().putAll(protectedHeader.asMap());
+        }
+        if (unprotectedHeader != null) {
+            if (!Collections.disjoint(unionHeaders.asMap().keySet(), 
+                                     unprotectedHeader.asMap().keySet())) {
+                throw new SecurityException("Protected and unprotected headers have duplicate values");
+            }
+            unionHeaders.asMap().putAll(unprotectedHeader.asMap());
+        }
+        
+        List<JweJsonEncryptionEntry> entries = new ArrayList<JweJsonEncryptionEntry>(encryptors.size());
+        Map<String, Object> jweJsonMap = new LinkedHashMap<String, Object>();
+        if (protectedHeader != null) {
+            jweJsonMap.put("protected", 
+                        Base64UrlUtility.encode(writer.toJson(protectedHeader)));
+        }
+        if (unprotectedHeader != null) {
+            jweJsonMap.put("unprotected", unprotectedHeader);
+        }
+        byte[] cipherText = null;
+        byte[] authTag = null;
+        for (int i = 0; i < encryptors.size(); i++) {
+            JweEncryptionProvider encryptor = encryptors.get(i);
+            JweHeaders perRecipientUnprotected = 
+                recepientUnprotected == null ? null : recepientUnprotected.get(i);
+            JweHeaders jsonHeaders = null;
+            if (recepientUnprotected != null) {
+                if (!Collections.disjoint(unionHeaders.asMap().keySet(), 
+                                          perRecipientUnprotected.asMap().keySet())) {
+                    throw new SecurityException("Protected and unprotected headers have duplicate values");
+                }
+                jsonHeaders = new JweHeaders(unionHeaders.asMap());
+                jsonHeaders.asMap().putAll(unprotectedHeader.asMap());
+            } else {  
+                jsonHeaders = null;
+            }
+            JweEncryptionInput input = new JweEncryptionInput(unionHeaders,
+                                                              cek,
+                                                              iv,
+                                                              aad);
+                
+            JweEncryptionState state = encryptor.createJweEncryptionState(input);
+            try {
+                byte[] currentCipherOutput = state.getCipher().doFinal(content);
+                byte[] currentCipherText = null;
+                byte[] currentAuthTag = null;
+                if (state.getAuthTagProducer() != null) {
+                    currentCipherText = currentCipherOutput;
+                    state.getAuthTagProducer().update(content, 0, content.length);
+                    currentAuthTag = state.getAuthTagProducer().getTag();
+                } else {
+                    final int authTagLengthBits = 128;
+                    final int cipherTextLen = currentCipherOutput.length - authTagLengthBits / 8;
+                    currentCipherText = Arrays.copyOf(currentCipherOutput, cipherTextLen);
+                    currentAuthTag = Arrays.copyOfRange(currentCipherOutput, cipherTextLen, authTagLengthBits / 8);
+                    if (cipherText == null) {
+                        cipherText = currentCipherText;
+                    } else if (!Arrays.equals(cipherText, currentCipherText)) {
+                        throw new SecurityException();
+                    }
+                    if (authTag == null) {
+                        authTag = currentAuthTag;
+                    } else if (!Arrays.equals(authTag, currentAuthTag)) {
+                        throw new SecurityException();
+                    }
+                }
+                
+                byte[] encryptedCek = state.getContentEncryptionKey(); 
+                if (encryptedCek == null && encryptor.getKeyAlgorithm() != null) {
+                    // can be null only if it is the direct key encryption
+                    throw new SecurityException();
+                }
+                String encodedCek = encryptedCek == null ? null : Base64UrlUtility.encode(encryptedCek);    
+                entries.add(new JweJsonEncryptionEntry(perRecipientUnprotected, encodedCek));
+            } catch (Exception ex) {
+                throw new SecurityException(ex);
+            }
+        }
+        jweJsonMap.put("recipients", entries);
+        if (aad != null) {
+            jweJsonMap.put("aad", Base64UrlUtility.encode(aad));
+        }
+        jweJsonMap.put("iv", Base64UrlUtility.encode(iv));
+        jweJsonMap.put("ciphertext", Base64UrlUtility.encode(cipherText));
+        jweJsonMap.put("tag", Base64UrlUtility.encode(authTag));
+        return writer.toJson(jweJsonMap);
+    }
+    private String checkAndGetContentAlgorithm(List<JweEncryptionProvider> encryptors) {
+        Set<String> set = new HashSet<String>();
+        for (JweEncryptionProvider encryptor : encryptors) {
+            set.add(encryptor.getContentAlgorithm());
+        }
+        if (set.size() != 1) {
+            throw new SecurityException("Invalid content encryption algorithm");
+        }
+        return set.iterator().next();
+    }
+    
+}