Skip to content

Commit

Permalink
issue #6 adding security utilies
Browse files Browse the repository at this point in the history
  • Loading branch information
jvermillard committed Apr 18, 2013
1 parent 8dff353 commit aac11ef
Show file tree
Hide file tree
Showing 10 changed files with 939 additions and 49 deletions.
7 changes: 7 additions & 0 deletions codec/pom.xml
Expand Up @@ -33,6 +33,13 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>

<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.47</version>
</dependency>

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
Expand Down
49 changes: 49 additions & 0 deletions codec/src/main/java/m3da/codec/EcdhService.java
@@ -0,0 +1,49 @@
/*******************************************************************************
* Copyright (c) 2013 Sierra Wireless.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Sierra Wireless - initial API and implementation
******************************************************************************/
package m3da.codec;

import java.security.KeyPair;

/**
* ECC Diffie-Hellman service.
*
* Implement Diffie-Hellman key negotiation for generating secure shared secrets.
*/
public interface EcdhService {

/**
* Generate a Elliptic curve Diffie–Hellman key pair (one public, one private)
*/
KeyPair generateEcdhKeyPair();

/**
* Extract the public certificate to be sent to the remote pair.
*/
byte[] getPublicKeyCertificate(KeyPair aKeyPair);

/**
* Produce the shared secret from your key pair and the other side public key x963 certificate.
*
* @param yourKeyPair your private/public ECDH key pair
* @param x963Cert the other side (remote device) public ECDH key X.963 certificate
* @return the shared secret
*/
byte[] computeSharedSecret(KeyPair yourKeyPair, byte[] x963Cert);

/**
* xor cipher the payload using the given ECCDH secret
*
* @param secret shared secret
* @param payload the content to cipher (must be 16 bytes long)
*/
byte[] cipherWithSecret(byte[] secret, byte[] payload);

}
10 changes: 10 additions & 0 deletions codec/src/main/java/m3da/codec/Hex.java
@@ -1,3 +1,13 @@
/*******************************************************************************
* Copyright (c) 2013 Sierra Wireless.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Sierra Wireless - initial API and implementation
******************************************************************************/
package m3da.codec;

public class Hex {
Expand Down
75 changes: 57 additions & 18 deletions codec/src/main/java/m3da/codec/M3daCodecService.java
Expand Up @@ -10,33 +10,72 @@
******************************************************************************/
package m3da.codec;

import java.io.InputStream;
import java.io.OutputStream;

import m3da.codec.dto.CipherAlgorithm;
import m3da.codec.dto.HmacType;

/**
* A service for encoding, decoding M3DA messages.
*
*/
public interface M3daCodecService {

/** The server name used for security computations */
public static final String SERVER_NAME = "AIRVANTAGE";
/** The available cipher operations */
public enum CipherMode {
ENCRYPTION, DECRYPTION
}

/** The server name used for security computations */
public static final String SERVER_NAME = "AIRVANTAGE";

/**
* Create a decoder for the M3DA envelope
*/
EnvelopeDecoder createEnvelopeDecoder();

/**
* Create an encoder for the M3DA envelope
*/
EnvelopeEncoder createEnvelopeEncoder();

/**
* Create a decoder for the M3DA envelope
*/
EnvelopeDecoder createEnvelopeDecoder();
/**
* Create decoder the envelope body
*/
BysantDecoder createBodyDecoder();

/**
* Create an encoder for the M3DA envelope
*/
EnvelopeEncoder createEnvelopeEncoder();
/**
* Create encoder the envelope body
*/
BysantEncoder createBodyEncoder();

/**
* Create decoder the envelope body
*/
BysantDecoder createBodyDecoder();
/**
* Compute the HMAC of a body using the M3DA RFC-2104 like algorithm
*
* @param algorithm the checksum algorithm to use (sha1,md5) for the HMAC
* @param username the username
* @param password the password
* @param salt the salt (e.g. nonce)
* @param messageBody the body to checksum
* @return the HMAC checksum value
*/
byte[] hmac(final HmacType algorithm, final byte[] username, final byte[] password, final byte[] salt,
final byte[] messageBody);

/**
* Create encoder the envelope body
*/
BysantEncoder createBodyEncoder();
/**
* Perform encryption or decryption on the data from a stream to another one.
* <p>
* The given password and nonce are used to compute the cipher key.
*
* @param cipherMode encryption or decryption
* @param algorithm the cryptographic algorithm to be used
* @param password the password
* @param nonce the nonce to use
* @param content the content to be ciphered/deciphered
* @param result the resulting content
*/
void cipher(final CipherMode cipherMode, final CipherAlgorithm algorithm, final byte[] password,
final byte[] nonce, final InputStream content, final OutputStream result);

}
184 changes: 184 additions & 0 deletions codec/src/main/java/m3da/codec/impl/EcdhServiceImpl.java
@@ -0,0 +1,184 @@
package m3da.codec.impl;

/*******************************************************************************
* Copyright (c) 2013 Sierra Wireless.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Sierra Wireless - initial API and implementation
******************************************************************************/
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.ECGenParameterSpec;
import java.util.Arrays;

import m3da.codec.EcdhService;
import m3da.codec.Hex;
import m3da.codec.M3daCodecServiceRuntimeException;

import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Implementation of {@link EcdhService}
*/
public class EcdhServiceImpl implements EcdhService {

private static final Logger LOG = LoggerFactory.getLogger(EcdhServiceImpl.class);

/** To be used for generating Elliptic curve Diffie–Hellman public/private key pairs */
private final KeyPairGenerator ecdhKeyGenerator;

public EcdhServiceImpl() {

this.registerSecurityProvider();

// preare the ECDH key pair generator
ECGenParameterSpec ecSpec = new ECGenParameterSpec("P-521");
try {
ecdhKeyGenerator = KeyPairGenerator.getInstance("ECDH", "BC");
ecdhKeyGenerator.initialize(ecSpec, new SecureRandom());
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(
"the used bouncycastle version should provide ECDH, bug ? check your dependencies", e);
} catch (NoSuchProviderException e) {
throw new IllegalStateException("bouncycastle should be provisioned, bug?", e);
} catch (InvalidAlgorithmParameterException e) {
throw new IllegalStateException("the code is probably broken", e);
}
}

/**
* {@inheritDoc}
*/
@Override
public KeyPair generateEcdhKeyPair() {
LOG.debug("generateEcdhKeyPair");
return ecdhKeyGenerator.generateKeyPair();
}

/**
* {@inheritDoc}
*/
@Override
public byte[] getPublicKeyCertificate(KeyPair aKeyPair) {
LOG.debug("getKeyPubliccertificate");
BCECPublicKey key = (BCECPublicKey) aKeyPair.getPublic();

// full X.509 certificate
byte[] fullCertificate = key.getEncoded();
return Arrays.copyOfRange(fullCertificate, 25, fullCertificate.length);
}

/**
* {@inheritDoc}
*/
@Override
public byte[] computeSharedSecret(KeyPair yourKeyPair, byte[] x963Cert) {
LOG.debug("computeSharedSecret( peyPair = {}, x963Cert = {})", yourKeyPair, x963Cert);
if (x963Cert[0] != 0x04) {
throw new M3daCodecServiceRuntimeException("The certificate should start with 0x04");
}
if (x963Cert.length % 2 != 1) {
throw new M3daCodecServiceRuntimeException("The certificate length should be odd");
}

int size = x963Cert.length / 2;

// extract the two point coordinate
ByteBuffer xBuff = ByteBuffer.allocate(size);
xBuff.order(ByteOrder.BIG_ENDIAN);
xBuff.put(x963Cert, 1, size);
xBuff.flip();

ByteBuffer yBuff = ByteBuffer.allocate(size);
yBuff.order(ByteOrder.BIG_ENDIAN);
yBuff.put(x963Cert, 1 + size, size);
yBuff.flip();

BCECPublicKey key = (BCECPublicKey) yourKeyPair.getPublic();
BCECPrivateKey privKey = (BCECPrivateKey) yourKeyPair.getPrivate();

// create point corresponding the the received public key
ECFieldElement x = new ECFieldElement.Fp(((ECFieldElement.Fp) key.getQ().getX()).getQ(), new BigInteger(
xBuff.array()));
ECFieldElement y = new ECFieldElement.Fp(((ECFieldElement.Fp) key.getQ().getY()).getQ(), new BigInteger(
yBuff.array()));

ECPoint point = new ECPoint.Fp(key.getParameters().getCurve(), x, y);

// compute the shared secret (ECDH magic)
ECPoint P = point.multiply(privKey.getD());

byte[] secret = leftPad(P.getX().toBigInteger().toByteArray(), 66);

if (LOG.isDebugEnabled()) {
LOG.debug("shared secret : {}", Hex.encodeHexString(secret));
}

return secret;
}

static final byte[] leftPad(byte[] src, int totalLength) {
byte[] padded = new byte[totalLength];
System.arraycopy(src, 0, padded, totalLength - src.length, src.length);
return padded;
}

/**
* {@inheritDoc}
*/
@Override
public byte[] cipherWithSecret(byte[] secret, byte[] payload) {
byte[] xorKey = md5(secret);
if (payload.length != xorKey.length) {
throw new IllegalArgumentException("payload must be 16 bytes long");
}
for (int i = 0; i < payload.length; i++) {
payload[i] ^= xorKey[i];
}
return payload;
}

/** Register BouncyCastle as JCE provider */
private void registerSecurityProvider() {
try {
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
LOG.info("Registration of BouncyCastle as a JCE provider succeeded");
} else {
LOG.warn("BouncyCastle already registered as a JCE provider");
}
} catch (Throwable t) {
throw new M3daCodecServiceRuntimeException("Failed to register BouncyCastle as JCE provider", t);
}
}

private byte[] md5(byte[] data) {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");

return digest.digest(data);

} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("no MD5 provider in the JVM");
}
}
}

0 comments on commit aac11ef

Please sign in to comment.