Index: jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/value/BinaryReferenceMessageTest.java =================================================================== --- jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/value/BinaryReferenceMessageTest.java (revision 0) +++ jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/value/BinaryReferenceMessageTest.java (revision 0) @@ -0,0 +1,40 @@ +/* + * 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.jackrabbit.value; + +import javax.jcr.Binary; + +import org.junit.Test; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; + +/** + * Testcase for {@link BinaryReferenceMessage} + */ +public class BinaryReferenceMessageTest { + + @Test + public void testParsing() throws Exception { + Binary binary = new BinaryImpl("message=20e00392babecb66deae2de6f556d011cdf437c8&signature=b0508a18ed7b3ed0d3391baf00f3c60eee3fb4ea&length=1203012".getBytes()); + BinaryReferenceMessage parsedMessage = BinaryReferenceMessage.readBinary(binary, "UTF-8"); + assertNotNull(parsedMessage); + assertEquals("20e00392babecb66deae2de6f556d011cdf437c8", parsedMessage.getMessage()); + assertEquals("b0508a18ed7b3ed0d3391baf00f3c60eee3fb4ea", parsedMessage.getSignature()); + assertEquals(1203012l, parsedMessage.getReferencedBinaryLength()); + } +} Property changes on: jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/value/BinaryReferenceMessageTest.java ___________________________________________________________________ Added: svn:eol-style + native Index: jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/util/HMACTest.java =================================================================== --- jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/util/HMACTest.java (revision 0) +++ jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/util/HMACTest.java (revision 0) @@ -0,0 +1,41 @@ +/* + * 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.jackrabbit.util; + +import org.junit.Test; + +import aQute.lib.hex.Hex; + +import static org.junit.Assert.assertEquals; + +/** + * Testcase for {@link HMAC} + */ +public class HMACTest { + + @Test + public void testCorrectDefaultCodeGeneration() throws Exception { + assertEquals("B180187B5E06C3C6ACDB22E04EF9413C3D15715D", + Hex.toHexString(HMAC.generateCode("hit that hot hat".getBytes(), "tattoo".getBytes()))); + } + + @Test + public void testCorrectSHA256CodeGeneration() throws Exception { + assertEquals("F49757F70114277DE7B24F40DE4E07D783A56C45BA1BEF463C9F686B0639B327", + Hex.toHexString(HMAC.generateCode("hit that hot hat".getBytes(), "tattoo".getBytes(), "HmacSHA256"))); + } +} Property changes on: jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/util/HMACTest.java ___________________________________________________________________ Added: svn:eol-style + native Index: jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/BinaryReferenceMessage.java =================================================================== --- jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/BinaryReferenceMessage.java (revision 0) +++ jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/BinaryReferenceMessage.java (revision 0) @@ -0,0 +1,166 @@ +/* + * 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.jackrabbit.value; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.jcr.Binary; +import javax.jcr.RepositoryException; + +import org.apache.commons.codec.binary.Hex; +import org.apache.jackrabbit.util.HMAC; + +/** + * A {@link BinaryReferenceMessage} consists of an (encrypted) DataIdentifier, + * the actual referenced {@link Binary}'s length, plus the signature of the + * DataIdentifier. + *

+ * The encryption key for the DataIdentifier is the the DataStoreSecret. + * The signature is the HMAC of the message, with the DataStoreSecret as the key. + * To simplify development/support, the message should be readable, for example, as an URL. + * Example (shortened): "message=0123456789abcd&signature=4567&length=1024". + *

+ * For now, we could use the following algorithms / formats: + * 128 bit DataStoreSecret ; AES-256 encryption / AES-CTR mode; HMAC-SHA-1. + */ +public class BinaryReferenceMessage implements Binary { + + private String message; + private String signature; + private long referencedBinaryLength; + private byte[] referenceMessage; + + public BinaryReferenceMessage(String contentIdentity, String key, long referencedBinaryLength) { + message = contentIdentity; // TODO : add AES encryption here + this.referencedBinaryLength = referencedBinaryLength; + try { + signature = Hex.encodeHexString(HMAC.generateCode(contentIdentity.getBytes(), key.getBytes())); + } catch (Exception e) { + throw new RuntimeException(e); + } + referenceMessage = ("message=" + message + "&signature=" + signature + "&length=" + referencedBinaryLength).getBytes(); + } + + private BinaryReferenceMessage() { + } + + private void setMessage(String message) { + this.message = message; + } + + private void setSignature(String signature) { + this.signature = signature; + } + + private void setReferenceMessage(byte[] referenceMessage) { + this.referenceMessage = referenceMessage; + } + + private void setReferencedBinaryLength(long referencedBinaryLength) { + this.referencedBinaryLength = referencedBinaryLength; + } + + private static final Pattern binaryMessagePattern = Pattern.compile("(message\\=)(\\w{40})(\\&signature\\=)(\\w{40})(&length\\=)(\\d+)"); + + public static BinaryReferenceMessage readBinary(Binary binary, String encoding) throws RepositoryException { + final char[] binaryBuffer = new char[(int) binary.getSize()]; + final StringBuilder out = new StringBuilder(); + try { + final Reader in = new InputStreamReader(binary.getStream(), encoding); + int charsRead; + try { + while ((charsRead = in.read(binaryBuffer, 0, binaryBuffer.length)) >= 0) { + out.append(binaryBuffer, 0, charsRead); + } + } finally { + in.close(); + } + } catch (Exception e) { + throw new RepositoryException(e); + } + String binaryString = out.toString(); + + Matcher matcher = binaryMessagePattern.matcher(binaryString); + if (matcher.matches()) { + BinaryReferenceMessage binaryReferenceMessage = new BinaryReferenceMessage(); + binaryReferenceMessage.setMessage(matcher.group(2)); + binaryReferenceMessage.setSignature(matcher.group(4)); + binaryReferenceMessage.setReferencedBinaryLength(Long.valueOf(matcher.group(6))); + binaryReferenceMessage.setReferenceMessage(matcher.group(0).getBytes()); + return binaryReferenceMessage; + } else { + throw new RepositoryException("the passed Binary is not a BinaryReerenceMessage"); + } + + } + + @Override + public InputStream getStream() throws RepositoryException { + checkDisposal(); + return new ByteArrayInputStream(referenceMessage); + } + + @Override + public int read(byte[] b, long position) throws IOException, RepositoryException { + checkDisposal(); + int length = Math.min(b.length, referenceMessage.length - (int) position); + if (length > 0) { + System.arraycopy(referenceMessage, (int) position, b, 0, length); + return length; + } else { + return -1; + } + } + + @Override + public long getSize() throws RepositoryException { + checkDisposal(); + return referenceMessage.length; + } + + @Override + public void dispose() { + referenceMessage = null; + message = null; + signature = null; + referencedBinaryLength = 0l; + } + + private void checkDisposal() { + if (referenceMessage == null) { + throw new IllegalStateException("this Binary has already been disposed"); + } + } + + public String getMessage() { + return message; + } + + public String getSignature() { + return signature; + } + + + public long getReferencedBinaryLength() { + return referencedBinaryLength; + } +} Property changes on: jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/BinaryReferenceMessage.java ___________________________________________________________________ Added: svn:eol-style + native Index: jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/HMAC.java =================================================================== --- jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/HMAC.java (revision 0) +++ jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/HMAC.java (revision 0) @@ -0,0 +1,40 @@ +/* + * 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.jackrabbit.util; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +/** + * Utility class to calculate the HMAC of a message, given a secret key. + * SHA-1 is used as default but other (MD5, SHA-256, etc.) can be used. + */ +public class HMAC { + + private static final String DEFAULT_ALGORITHM = "HmacSHA1"; + + public static byte[] generateCode(byte[] message, final byte[] key) throws Exception { + return generateCode(message, key, DEFAULT_ALGORITHM); + } + + public static byte[] generateCode(byte[] message, final byte[] key, String algorithm) throws Exception { + Mac mac = Mac.getInstance(algorithm); + SecretKeySpec k = new SecretKeySpec(key, algorithm); + mac.init(k); + return mac.doFinal(message); + } +} Property changes on: jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/HMAC.java ___________________________________________________________________ Added: svn:eol-style + native Index: jackrabbit-jcr-commons/pom.xml =================================================================== --- jackrabbit-jcr-commons/pom.xml (revision 1478286) +++ jackrabbit-jcr-commons/pom.xml (working copy) @@ -104,6 +104,11 @@ provided + commons-codec + commons-codec + provided + + junit junit test Index: jackrabbit-core/src/test/repository/repository.xml =================================================================== --- jackrabbit-core/src/test/repository/repository.xml (revision 1478286) +++ jackrabbit-core/src/test/repository/repository.xml (working copy) @@ -30,7 +30,9 @@ - + + +