From 5c42b8fe64c371f50a5c56a3c521db6e06292fd7 Mon Sep 17 00:00:00 2001 From: Patrick Boyd Date: Tue, 31 Aug 2021 14:04:04 -0500 Subject: [PATCH] Fix #665: Allow JCE KDF to work (#666) * Fix #665: Allow JCE KDF to work * Add header * Add KDF unit test --- .../security/DerivationFunction.java | 2 +- .../security/jce/JceDerivationFunction.java | 3 +- .../jce/JceDerivationFunctionFactory.java | 53 ++++++++++++ .../security/jce/JceSecurityProvider.java | 2 +- .../KDFCounterHMacSHA256.java | 80 +++++++++++++++++++ .../KDFCounterHMacSHA256.groovy | 39 +++++++++ 6 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/hierynomus/security/jce/JceDerivationFunctionFactory.java create mode 100644 src/main/java/com/hierynomus/security/jce/derivationfunction/KDFCounterHMacSHA256.java create mode 100644 src/test/groovy/com/hierynomus/security/jce/derivationfunction/KDFCounterHMacSHA256.groovy diff --git a/src/main/java/com/hierynomus/security/DerivationFunction.java b/src/main/java/com/hierynomus/security/DerivationFunction.java index 5123385c..b8403e39 100644 --- a/src/main/java/com/hierynomus/security/DerivationFunction.java +++ b/src/main/java/com/hierynomus/security/DerivationFunction.java @@ -19,7 +19,7 @@ public interface DerivationFunction { - void init(DerivationParameters parameters); + void init(DerivationParameters parameters) throws SecurityException; int generateBytes(byte[] out, int outOff, int len); } diff --git a/src/main/java/com/hierynomus/security/jce/JceDerivationFunction.java b/src/main/java/com/hierynomus/security/jce/JceDerivationFunction.java index 55db88c0..1fe69149 100644 --- a/src/main/java/com/hierynomus/security/jce/JceDerivationFunction.java +++ b/src/main/java/com/hierynomus/security/jce/JceDerivationFunction.java @@ -16,12 +16,13 @@ package com.hierynomus.security.jce; import com.hierynomus.security.DerivationFunction; +import com.hierynomus.security.SecurityException; import com.hierynomus.security.jce.derivationfunction.DerivationParameters; public class JceDerivationFunction implements DerivationFunction { @Override - public void init(DerivationParameters parameters) { + public void init(DerivationParameters parameters) throws SecurityException { throw new UnsupportedOperationException(); } diff --git a/src/main/java/com/hierynomus/security/jce/JceDerivationFunctionFactory.java b/src/main/java/com/hierynomus/security/jce/JceDerivationFunctionFactory.java new file mode 100644 index 00000000..e0403ef5 --- /dev/null +++ b/src/main/java/com/hierynomus/security/jce/JceDerivationFunctionFactory.java @@ -0,0 +1,53 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed 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 com.hierynomus.security.jce; + +import com.hierynomus.protocol.commons.Factory; +import com.hierynomus.security.DerivationFunction; +import com.hierynomus.security.jce.derivationfunction.KDFCounterHMacSHA256; + +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; + +public class JceDerivationFunctionFactory { + private static final Map> lookup = new HashMap<>(); + + static { + lookup.put("KDF/Counter/HMACSHA256", new Factory() { + @Override + public DerivationFunction create() { + try { + return new KDFCounterHMacSHA256(); + } catch(NoSuchAlgorithmException ex) { + return null; + } + } + }); + } + + public static DerivationFunction create(String name) { + Factory derivationFunctionFactory = lookup.get(name); + if (derivationFunctionFactory == null) { + throw new IllegalArgumentException("Unknown DerivationFunction " + name); + } + DerivationFunction func = derivationFunctionFactory.create(); + if (func == null) { + throw new IllegalArgumentException("DerivationFunction " + name + " not supported!"); + } + return func; + } +} diff --git a/src/main/java/com/hierynomus/security/jce/JceSecurityProvider.java b/src/main/java/com/hierynomus/security/jce/JceSecurityProvider.java index b7aa03b7..1d94b985 100644 --- a/src/main/java/com/hierynomus/security/jce/JceSecurityProvider.java +++ b/src/main/java/com/hierynomus/security/jce/JceSecurityProvider.java @@ -61,6 +61,6 @@ public AEADBlockCipher getAEADBlockCipher(String name) throws SecurityException{ @Override public DerivationFunction getDerivationFunction(String name) throws SecurityException { - throw new UnsupportedOperationException("Key Derivation Function is currently only supported when using the BCSecurityProvider. Please configure: `SmbConfig.withSecurityProvider(new BCSecurityProvider())` to use SMB3 support."); + return JceDerivationFunctionFactory.create(name); } } diff --git a/src/main/java/com/hierynomus/security/jce/derivationfunction/KDFCounterHMacSHA256.java b/src/main/java/com/hierynomus/security/jce/derivationfunction/KDFCounterHMacSHA256.java new file mode 100644 index 00000000..61429be7 --- /dev/null +++ b/src/main/java/com/hierynomus/security/jce/derivationfunction/KDFCounterHMacSHA256.java @@ -0,0 +1,80 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed 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 com.hierynomus.security.jce.derivationfunction; + +import com.hierynomus.security.SecurityException; +import com.hierynomus.security.jce.JceDerivationFunction; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +public class KDFCounterHMacSHA256 extends JceDerivationFunction { + private Mac mac; + private byte[] fixedSuffix; + private int maxLength; + + public KDFCounterHMacSHA256() throws NoSuchAlgorithmException { + mac = Mac.getInstance("HmacSHA256"); + } + + @Override + public void init(DerivationParameters parameters) throws SecurityException { + if (!(parameters instanceof CounterDerivationParameters)) { + throw new IllegalArgumentException("Parameters should be a CounterDerivationParameters"); + } + + CounterDerivationParameters p = (CounterDerivationParameters) parameters; + SecretKeySpec seed = new SecretKeySpec(p.getSeed(), "HmacSHA256"); + try { + mac.init(seed); + } catch(InvalidKeyException ex) { + throw new SecurityException(ex); + } + this.fixedSuffix = p.getFixedCounterSuffix(); + this.maxLength = p.getCounterLength(); + } + + @Override + public int generateBytes(byte[] out, int outOff, int len) { + int generated = 0; + //The number of rounds is the output length divided by the size (in bytes of the function output) + int rounds = len/32; + if ((len % 32) != 0) { + //Do one more round for the odd bytes + rounds++; + } + byte[] input = new byte[4]; + for (int i = 0; i < rounds; i++) { + input[0] = (byte)((i+1) >>> 24); + input[1] = (byte)((i+1) >>> 16); + input[2] = (byte)((i+1) >>> 8); + input[3] = (byte)(i+1); + mac.update(input); + mac.update(this.fixedSuffix); + byte[] tmp = mac.doFinal(); + int toCopy = tmp.length; + if ((tmp.length + generated) > len) { + toCopy = len - generated; + } + System.arraycopy(tmp, 0, out, outOff, toCopy); + generated += toCopy; + outOff += toCopy; + } + return len; + } +} \ No newline at end of file diff --git a/src/test/groovy/com/hierynomus/security/jce/derivationfunction/KDFCounterHMacSHA256.groovy b/src/test/groovy/com/hierynomus/security/jce/derivationfunction/KDFCounterHMacSHA256.groovy new file mode 100644 index 00000000..a3d649ec --- /dev/null +++ b/src/test/groovy/com/hierynomus/security/jce/derivationfunction/KDFCounterHMacSHA256.groovy @@ -0,0 +1,39 @@ +/* + * Copyright (C)2016 - SMBJ Contributors + * + * Licensed 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 com.hierynomus.security.jce.derivationfunction + +import spock.lang.Specification +import org.codehaus.groovy.runtime.EncodingGroovyMethods + +class KDFCounterHMacSHA256Spec extends Specification { + + def "KDF Counter tests"() { + when: + def df = new KDFCounterHMacSHA256() + def params = new CounterDerivationParameters(EncodingGroovyMethods.decodeHex(seed), EncodingGroovyMethods.decodeHex(suffix), 32) + df.init(params) + byte[] derived = new byte[16]; + df.generateBytes(derived, 0, derived.length) + + then: + derived == EncodingGroovyMethods.decodeHex(expectedResults) + + where: + seed << ["05748462F987037190DEF58A165E3678", "05748462F987037190DEF58A165E3678", "C3ACFDC1B070770A8DDAB9740DA29B79"] + suffix << ["534D425369676E696E674B6579000043F965A710069C1CEC7D79469C0FDE7143FB350599997C65D2B5D65B40DED490C0E13BA9F5822D2619BFEB08873909926F4BBE455321DC4C151A46B47718421F00000080" , "534D424332534369706865724B6579000043F965A710069C1CEC7D79469C0FDE7143FB350599997C65D2B5D65B40DED490C0E13BA9F5822D2619BFEB08873909926F4BBE455321DC4C151A46B47718421F00000080", "534D425369676E696E674B65790000FE8742AB31DC7A88DF45BCA328875D079E597CF711D3AFB397AF32422E9BD8541C1F0E1D665646B56A141BE700351C35FB7426F9946F22271DE0B4EDFAFBC11E00000080"] + expectedResults << ["E4F496372BB7FBA2BFCAE08AA07C9C16", "17566BCB45012959EBE074736B0EBD79", "14595AE1720357BBA5B22084041E27E9"] + } +} \ No newline at end of file