Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/com/google/cose/CoseKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,18 @@ protected Map compile() throws CoseException {
return cborKey;
}

public T copyFrom(CoseKey key) {
keyType = key.keyType;
keyId = key.keyId;
algorithm = (key.algorithm == null) ? null : Algorithm.fromCoseAlgorithmId(key.algorithm);
operations.clear();
if (key.operations != null) {
operations.addAll(key.operations);
}
baseIv = key.baseIv;
return self();
}

public T withKeyType(int keyType) {
this.keyType = keyType;
return self();
Expand Down
174 changes: 168 additions & 6 deletions src/com/google/cose/Ec2Key.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,23 @@
import com.google.cose.utils.CoseUtils;
import com.google.cose.utils.Headers;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECPoint;

/**
* Abstract class for generic Ec2 key
*/
public abstract class Ec2Key extends CoseKey {
private static final int SIGN_POSITIVE = 1;

private ECPublicKey publicKey;
private int curve;
protected KeyPair keyPair;

Ec2Key(DataItem cborKey) throws CborException, CoseException {
super(cborKey);
Expand All @@ -33,10 +41,29 @@ void populateKeyFromCbor() throws CborException, CoseException {
}

// Get curve information
int curve = CborUtils.asInteger(labels.get(Headers.KEY_PARAMETER_CURVE));
curve = CborUtils.asInteger(labels.get(Headers.KEY_PARAMETER_CURVE));

// Get private key.
final ECPrivateKey privateKey;
if (labels.containsKey(Headers.KEY_PARAMETER_D)) {
byte[] key = CborUtils.asByteString(labels.get(Headers.KEY_PARAMETER_D)).getBytes();
if (key.length == 0) {
throw new CoseException("Cannot decode private key. Missing coordinate information.");
}
privateKey = CoseUtils.getEc2PrivateKeyFromInteger(curve, new BigInteger(SIGN_POSITIVE, key));
} else {
privateKey = null;
}

if (!labels.containsKey(Headers.KEY_PARAMETER_X)) {
throw new CoseException(CoseException.MISSING_KEY_MATERIAL_EXCEPTION_MESSAGE);
if (privateKey == null) {
throw new CoseException(CoseException.MISSING_KEY_MATERIAL_EXCEPTION_MESSAGE);
} else {
keyPair = new KeyPair(
CoseUtils.getEc2PublicKeyFromPrivateKey(curve, privateKey),
privateKey);
return;
}
}

final ByteString xCor = CborUtils.asByteString(labels.get(Headers.KEY_PARAMETER_X));
Expand All @@ -46,15 +73,22 @@ void populateKeyFromCbor() throws CborException, CoseException {
throw new IllegalStateException("X coordinate provided but Y coordinate is missing.");
}
final ByteString yCor = CborUtils.asByteString(labels.get(Headers.KEY_PARAMETER_Y));
publicKey = (ECPublicKey) CoseUtils.getEc2PublicKeyFromCoordinates(
final PublicKey publicKey = CoseUtils.getEc2PublicKeyFromCoordinates(
curve,
new BigInteger(SIGN_POSITIVE, xCor.getBytes()),
new BigInteger(SIGN_POSITIVE, yCor.getBytes())
);
keyPair = new KeyPair(publicKey, privateKey);
}

public ECPublicKey getPublicKey() {
return publicKey;
return (ECPublicKey) this.keyPair.getPublic();
}

public abstract Ec2Key getPublic() throws CborException, CoseException;

public int getCurve() {
return curve;
}

void verifyAlgorithmAllowedByKey(Algorithm algorithm) throws CborException, CoseException {
Expand All @@ -79,9 +113,32 @@ void verifyAlgorithmAllowedByKey(Algorithm algorithm) throws CborException, Cose
}
}

// Big endian: Do not reuse for little endian encodings
private static byte[] arrayFromBigNum(BigInteger num, int keySize)
throws IllegalArgumentException {
// Roundup arithmetic from bits to bytes.
byte[] keyBytes = new byte[(keySize + 7) / 8];
byte[] keyBytes2 = num.toByteArray();
if (keyBytes.length == keyBytes2.length) {
return keyBytes2;
}
if (keyBytes2.length > keyBytes.length) {
// There should be no more than one padding(0) byte, invalid key otherwise.
if (keyBytes2.length - keyBytes.length > 1 && keyBytes2[0] != 0) {
throw new IllegalArgumentException();
}
System.arraycopy(keyBytes2, keyBytes2.length - keyBytes.length, keyBytes, 0, keyBytes.length);
} else {
System.arraycopy(
keyBytes2, 0, keyBytes, keyBytes.length - keyBytes2.length, keyBytes2.length);
}
return keyBytes;
}

/** Recursive builder to build out the Ec2 key and its subclasses. */
abstract static class Builder<T extends Builder<T>> extends CoseKey.Builder<T> {
private Integer curve = null;
private byte[] dParameter;
private byte[] xCor;
private byte[] yCor;

Expand All @@ -96,7 +153,7 @@ void verifyKeyMaterialPresentAndComplete() throws CoseException {
}

boolean isKeyMaterialPresent() {
return xCor != null && yCor != null;
return dParameter != null || (xCor != null && yCor != null);
}

boolean isPublicKeyMaterialIncomplete() {
Expand All @@ -113,6 +170,9 @@ protected Map compile() throws CoseException {
Map cborKey = super.compile();

cborKey.put(new NegativeInteger(Headers.KEY_PARAMETER_CURVE), new UnsignedInteger(curve));
if (dParameter != null) {
cborKey.put(new NegativeInteger(Headers.KEY_PARAMETER_D), new ByteString(dParameter));
}
if (xCor != null) {
cborKey.put(new NegativeInteger(Headers.KEY_PARAMETER_X), new ByteString(xCor));
}
Expand All @@ -122,6 +182,30 @@ protected Map compile() throws CoseException {
return cborKey;
}

public T copyFrom(Ec2Key key) {
curve = key.curve;
int keySize;
switch (curve) {
case Headers.CURVE_EC2_P256:
keySize = 256;
break;
case Headers.CURVE_EC2_P384:
keySize = 384;
break;
case Headers.CURVE_EC2_P521:
keySize = 521;
break;
default:
throw new AssertionError();
}
ECPublicKey pubKey = (ECPublicKey) key.keyPair.getPublic();
ECPrivateKey privKey = (ECPrivateKey) key.keyPair.getPrivate();
xCor = arrayFromBigNum(pubKey.getW().getAffineX(), keySize);
yCor = arrayFromBigNum(pubKey.getW().getAffineY(), keySize);
dParameter = (privKey == null) ? null : privKey.getS().toByteArray();
return super.copyFrom(key);
}

public T withCurve(int curve) throws CoseException {
if ((curve < 0) || (curve > Headers.CURVE_EC2_P521)) {
throw new CoseException(CoseException.UNSUPPORTED_CURVE_EXCEPTION_MESSAGE);
Expand All @@ -139,5 +223,83 @@ public T withYCoordinate(byte[] yCor) {
this.yCor = yCor;
return self();
}

public T withGeneratedKeyPair(int curve) throws CoseException {
KeyPair keyPair;
int keySize;
String curveName;

switch (curve) {
case Headers.CURVE_EC2_P256:
curveName = "secp256r1";
keySize = 256;
break;

case Headers.CURVE_EC2_P384:
curveName = "secp384r1";
keySize = 384;
break;

case Headers.CURVE_EC2_P521:
curveName = "secp521r1";
keySize = 521;
break;

default:
throw new CoseException("Unsupported curve: " + curve);
}
try {
ECGenParameterSpec paramSpec = new ECGenParameterSpec(curveName);
KeyPairGenerator gen = KeyPairGenerator.getInstance("EC");
gen.initialize(paramSpec);
keyPair = gen.genKeyPair();
ECPoint pubPoint = ((ECPublicKey) keyPair.getPublic()).getW();

return withPrivateKeyRepresentation()
.withPkcs8EncodedBytes(keyPair.getPrivate().getEncoded())
.withXCoordinate(arrayFromBigNum(pubPoint.getAffineX(), keySize))
.withYCoordinate(arrayFromBigNum(pubPoint.getAffineY(), keySize))
.withCurve(curve);
} catch (GeneralSecurityException e) {
throw new CoseException("Failed to generate key pari for curve: " + curve, e);
}
}

public PrivateKeyRepresentationBuilder<T> withPrivateKeyRepresentation() {
return new PrivateKeyRepresentationBuilder<T>(this);
}

/**
* Helper class to get the raw bytes out of the encoded private keys.
*/
public static class PrivateKeyRepresentationBuilder<T extends Builder<T>> {
Builder<T> builder;

PrivateKeyRepresentationBuilder(Builder<T> builder) {
this.builder = builder;
}

public T withPrivateKey(ECPrivateKey privateKey) {
builder.dParameter = privateKey.getS().toByteArray();
return builder.self();
}

public T withPkcs8EncodedBytes(byte[] keyBytes) throws CoseException {
ECPrivateKey key = CoseUtils.getEc2PrivateKeyFromEncodedKeyBytes(keyBytes);
builder.dParameter = key.getS().toByteArray();
return builder.self();
}

/**
* This function expects the BigInteger byte array of the private key. This is typically the
* multiplier in the EC2 private key which can generate EC2 public key from generator point.
* @param rawBytes byte array representation of BigInteger
* @return {@link Builder}
*/
public T withDParameter(byte[] rawBytes) {
builder.dParameter = rawBytes;
return builder.self();
}
}
}
}
25 changes: 25 additions & 0 deletions src/com/google/cose/Ec2KeyAgreementKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import co.nstant.in.cbor.CborException;
import co.nstant.in.cbor.model.DataItem;
import com.google.cose.exceptions.CoseException;
import com.google.cose.utils.Algorithm;
import com.google.cose.utils.CborUtils;
import com.google.cose.utils.Headers;

Expand All @@ -42,6 +43,30 @@ public static Ec2KeyAgreementKey decode(DataItem cborKey) throws CborException,
return new Ec2KeyAgreementKey(cborKey);
}

@Override
public Ec2KeyAgreementKey getPublic() throws CborException, CoseException {
if (keyPair.getPrivate() == null) {
return this;
} else {
return builder().copyFrom(this).withPrivateKeyRepresentation().withDParameter(null).build();
}
}

/** Generates a COSE formatted Ec2 key agreement key given a specific algorithm and curve. */
public static Ec2KeyAgreementKey generateKey(Algorithm algorithm, int curve)
throws CborException, CoseException {
switch (algorithm) {
case ECDH_ES_HKDF_256:
return builder()
.withGeneratedKeyPair(curve)
.withAlgorithm(algorithm)
.build();

default:
throw new CoseException("Unsupported algorithm: " + algorithm.getJavaAlgorithmId());
}
}

public static class Builder extends Ec2Key.Builder<Builder> {
@Override
public Builder self() {
Expand Down
Loading