Skip to content

Commit

Permalink
Core: Support for ECDSA JOSE signatures (#614)
Browse files Browse the repository at this point in the history
  • Loading branch information
hvge committed Aug 28, 2024
1 parent 419ef7b commit d0191fc
Show file tree
Hide file tree
Showing 21 changed files with 480 additions and 32 deletions.
39 changes: 34 additions & 5 deletions include/PowerAuth/PublicTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -386,24 +386,53 @@ namespace powerAuth
HMAC_Activation = 3
};

enum SignatureFormat
{
/**
If default signature is used, then `ECDSA_DER` is used for ECDSA signature.
The raw bytes are always used for HMAC signatures.
*/
Default = 0,
/**
ECDSA signature in DER format is expected at input, or produced at output:
```
ASN.1 notation:
ECDSASignature ::= SEQUENCE {
r INTEGER,
s INTEGER
}
```
*/
ECDSA_DER = 1,
/**
ECDSA signature in JOSE format is epxpected at input, or produced at output.
*/
ECDSA_JOSE = 2,
};

/**
A key type used for signature calculation.
*/
SigningKey signingKey;
/**
An arbitrary data
Format of signature expected at input, or produced at output.
*/
SignatureFormat signatureFormat;
/**
An arbitrary data.
*/
cc7::ByteArray data;
/**
A signagure calculated for data
A signagure calculated for data.
*/
cc7::ByteArray signature;

/**
Default constructor
Default constructor.
*/
SignedData(SigningKey signingKey = ECDSA_MasterServerKey) :
signingKey(signingKey)
SignedData(SigningKey signingKey = ECDSA_MasterServerKey, SignatureFormat signatureFormat = Default) :
signingKey(signingKey),
signatureFormat(signatureFormat)
{
}

Expand Down
3 changes: 2 additions & 1 deletion include/PowerAuth/Session.h
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,8 @@ namespace powerAuth
EC_WrongParam, if some required parameter is missing
*/
ErrorCode signDataWithDevicePrivateKey(const std::string & c_vault_key, const SignatureUnlockKeys & keys,
const cc7::ByteRange & data, cc7::ByteArray & out_signature);
const cc7::ByteRange & data, SignedData::SignatureFormat out_format,
cc7::ByteArray & out_signature);

private:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -477,10 +477,11 @@ public byte[] prepareKeyValueDictionaryForDataSigning(Map<String, String> keyVal
* @param cVaultKey encrypted vault key
* @param unlockKeys unlock keys object with required possession factor
* @param data data to be signed
* @param signatureFormat Format of produced signature.
*
* @return array of bytes with calculated signature or null in case of failure.
*/
public native byte[] signDataWithDevicePrivateKey(String cVaultKey, SignatureUnlockKeys unlockKeys, byte[] data);
public native byte[] signDataWithDevicePrivateKey(String cVaultKey, SignatureUnlockKeys unlockKeys, byte[] data, @SignatureFormat int signatureFormat);

//
// External encryption key
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2024 Wultra s.r.o.
*
* 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 io.getlime.security.powerauth.core;

import androidx.annotation.IntDef;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import static io.getlime.security.powerauth.core.SignatureFormat.DEFAULT;
import static io.getlime.security.powerauth.core.SignatureFormat.ECDSA_DER;
import static io.getlime.security.powerauth.core.SignatureFormat.ECDSA_JOSE;

/**
* The {@code SignatureFormat} enumeration defines signature type expected at input, or produced at output.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({DEFAULT, ECDSA_DER, ECDSA_JOSE})
public @interface SignatureFormat {
/**
* If default signature is used, then `ECDSA_DER` is used for ECDSA signature. The raw bytes are always used for
* HMAC signatures.
*/
int DEFAULT = 0;
/**
* ECDSA signature in DER format is expected at input, or produced at output:
* <pre>
* ASN.1 notation:
* ECDSASignature ::= SEQUENCE {
* r INTEGER,
* s INTEGER
* }
* </pre>
*/
int ECDSA_DER = 1;
/**
* ECDSA signature in JOSE format is epxpected at input, or produced at output.
*/
int ECDSA_JOSE = 2;
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,22 @@ public class SignedData {
*/
@SigningDataKey
public final int signingKey;
/**
* Format of signature expected at input, or produced at output.
*/
@SignatureFormat
public final int signatureFormat;

/**
* @param data data protected with signature
* @param signature signature calculated for data
* @param signingKey Key used to sign data, or will be used for the signature calculation.
* @param signatureFormat Format of signature expected at input, or produced at output.
*/
public SignedData(byte[] data, byte[] signature, @SigningDataKey int signingKey) {
public SignedData(byte[] data, byte[] signature, @SigningDataKey int signingKey, @SignatureFormat int signatureFormat) {
this.data = data;
this.signature = signature;
this.signingKey = signingKey;
this.signatureFormat = signatureFormat;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import com.google.gson.reflect.TypeToken;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.ReentrantLock;
Expand Down Expand Up @@ -1791,7 +1790,7 @@ public boolean verifyServerSignedData(byte[] data, byte[] signature, boolean use

// Verify signature
final int signingKey = useMasterKey ? SigningDataKey.ECDSA_MASTER_SERVER_KEY : SigningDataKey.ECDSA_PERSONALIZED_KEY;
final SignedData signedData = new SignedData(data, signature, signingKey);
final SignedData signedData = new SignedData(data, signature, signingKey, SignatureFormat.ECDSA_DER);
return mSession.verifyServerSignedData(signedData) == ErrorCode.OK;
}

Expand All @@ -1805,15 +1804,28 @@ public boolean verifyServerSignedData(byte[] data, byte[] signature, boolean use
*/
public @Nullable
ICancelable signDataWithDevicePrivateKey(@NonNull final Context context, @NonNull PowerAuthAuthentication authentication, @NonNull final byte[] data, @NonNull final IDataSignatureListener listener) {
return signDataWithDevicePrivateKeyImpl(context, authentication, data, SignatureFormat.ECDSA_DER, listener);
}

/**
* Sign provided data with a private key that is stored in secure vault.
* @param context Context.
* @param authentication Authentication object for vault unlock request.
* @param data Data to be signed.
* @param signatureFormat Format of output signature.
* @param listener Listener with callbacks to signature status.
* @return Async task associated with vault unlock request.
*/
private @Nullable
ICancelable signDataWithDevicePrivateKeyImpl(@NonNull final Context context, @NonNull PowerAuthAuthentication authentication, @NonNull final byte[] data, @SignatureFormat int signatureFormat, @NonNull final IDataSignatureListener listener) {
// Fetch vault encryption key using vault unlock request.
return this.fetchEncryptedVaultUnlockKey(context, authentication, VaultUnlockReason.SIGN_WITH_DEVICE_PRIVATE_KEY, new IFetchEncryptedVaultUnlockKeyListener() {
@Override
public void onFetchEncryptedVaultUnlockKeySucceed(String encryptedEncryptionKey) {
if (encryptedEncryptionKey != null) {
// Let's sign the data
SignatureUnlockKeys keys = new SignatureUnlockKeys(deviceRelatedKey(context), null, null);
byte[] signature = mSession.signDataWithDevicePrivateKey(encryptedEncryptionKey, keys, data);
byte[] signature = mSession.signDataWithDevicePrivateKey(encryptedEncryptionKey, keys, data, signatureFormat);
// Propagate error
if (signature != null) {
listener.onDataSignedSucceed(signature);
Expand All @@ -1830,8 +1842,10 @@ public void onFetchEncryptedVaultUnlockKeyFailed(Throwable t) {
listener.onDataSignedFailed(t);
}
});

}


/**
* Change the password using local re-encryption, do not validate old password by calling any endpoint.
*
Expand Down Expand Up @@ -2535,7 +2549,7 @@ public ICancelable signJwtWithDevicePrivateKey(@NonNull Context context, @NonNul
final String jwtHeader = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9"; // {"alg":"ES256","typ":"JWT"}
final String jwtClaims = serialization.serializeJwtObject(claims);
final String jwtHeaderAndClaims = jwtHeader + "." + jwtClaims;
return signDataWithDevicePrivateKey(context, authentication, jwtHeaderAndClaims.getBytes(StandardCharsets.US_ASCII), new IDataSignatureListener() {
return signDataWithDevicePrivateKeyImpl(context, authentication, jwtHeaderAndClaims.getBytes(StandardCharsets.US_ASCII), SignatureFormat.ECDSA_JOSE, new IDataSignatureListener() {
@Override
public void onDataSignedSucceed(@NonNull byte[] signature) {
// Encoded signature
Expand Down
3 changes: 2 additions & 1 deletion proj-xcode/PowerAuthCore/PowerAuthCoreSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,8 @@
*/
- (nullable NSData*) signDataWithDevicePrivateKey:(nonnull NSString*)cVaultKey
keys:(nonnull PowerAuthCoreSignatureUnlockKeys*)unlockKeys
data:(nonnull NSData*)data;
data:(nonnull NSData*)data
format:(PowerAuthCoreSignatureFormat)format;

#pragma mark - External Encryption Key

Expand Down
4 changes: 3 additions & 1 deletion proj-xcode/PowerAuthCore/PowerAuthCoreSession.mm
Original file line number Diff line number Diff line change
Expand Up @@ -380,15 +380,17 @@ - (nullable NSData*) deriveCryptographicKeyFromVaultKey:(nonnull NSString*)cVaul
- (nullable NSData*) signDataWithDevicePrivateKey:(nonnull NSString*)cVaultKey
keys:(nonnull PowerAuthCoreSignatureUnlockKeys*)unlockKeys
data:(nonnull NSData*)data
format:(PowerAuthCoreSignatureFormat)format
{
REQUIRE_READ_ACCESS();
std::string cpp_c_vault_key = cc7::objc::CopyFromNSString(cVaultKey);
cc7::ByteArray cpp_data = cc7::objc::CopyFromNSData(data);
auto cpp_format = static_cast<SignedData::SignatureFormat>(format);
SignatureUnlockKeys cpp_keys;
PowerAuthCoreSignatureUnlockKeysToStruct(unlockKeys, cpp_keys);

cc7::ByteArray cpp_signature;
auto error = _session->signDataWithDevicePrivateKey(cpp_c_vault_key, cpp_keys, cpp_data, cpp_signature);
auto error = _session->signDataWithDevicePrivateKey(cpp_c_vault_key, cpp_keys, cpp_data, cpp_format, cpp_signature);
if (error == EC_Ok) {
return cc7::objc::CopyToNSData(cpp_signature);
}
Expand Down
35 changes: 34 additions & 1 deletion proj-xcode/PowerAuthCore/PowerAuthCoreTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -318,12 +318,45 @@ typedef NS_ENUM(int, PowerAuthCoreSigningDataKey) {
PowerAuthCoreSigningDataKey_HMAC_Activation = 3
};

/**
The `PowerAuthCoreSignatureFormat` enumeration defines signature type expected at input, or produced
at output.
*/
typedef NS_ENUM(int, PowerAuthCoreSignatureFormat) {
/**
If used, then `PowerAuthCoreSignatureFormat_ECDSA_DER` is used for ECDSA signature.
For the HMAC signature, the raw bytes is always used.
*/
PowerAuthCoreSignatureFormat_Default = 0,
/**
ECDSA signature in DER format is expected at input, or produced at output:
```
// ASN.1 notation:
ECDSASignature ::= SEQUENCE {
r INTEGER,
s INTEGER
}
```
*/
PowerAuthCoreSignatureFormat_ECDSA_DER = 1,
/**
ECDSA signature in JOSE format is epxpected at input, or produced at output.
*/
PowerAuthCoreSignatureFormat_ECDSA_JOSE = 2
};

/**
The PowerAuthCoreSignedData object contains data and signature calculated from data.
*/
@interface PowerAuthCoreSignedData : NSObject

/**
A signign key to use.
*/
@property (nonatomic, assign) PowerAuthCoreSigningDataKey signingDataKey;
/**
A format of signature expected at input or produced at output.
*/
@property (nonatomic, assign) PowerAuthCoreSignatureFormat signatureFormat;
/**
A data protected with signature
*/
Expand Down
13 changes: 12 additions & 1 deletion proj-xcode/PowerAuthCore/PowerAuthCoreTypes.mm
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,17 @@ - (void) setSigningDataKey:(PowerAuthCoreSigningDataKey)signingDataKey
_signedData.signingKey = static_cast<SignedData::SigningKey>(signingDataKey);
}

// Signature type

- (PowerAuthCoreSignatureFormat) signatureFormat
{
return static_cast<PowerAuthCoreSignatureFormat>(_signedData.signatureFormat);
}

- (void) setSignatureFormat:(PowerAuthCoreSignatureFormat)signatureType
{
_signedData.signatureFormat = static_cast<SignedData::SignatureFormat>(signatureType);
}

// Bytes setters and getters

Expand Down Expand Up @@ -204,7 +215,7 @@ - (void) setSignatureBase64:(NSString *)signatureBase64
#ifdef DEBUG
- (NSString*) description
{
return [NSString stringWithFormat:@"<PowerAuthCoreSignedData data=%@, signature=%@>", self.dataBase64, self.signatureBase64];
return [NSString stringWithFormat:@"<PowerAuthCoreSignedData key=%d, data=%@, signature=%@>", _signedData.signingKey, self.dataBase64, self.signatureBase64];
}
#endif

Expand Down
20 changes: 17 additions & 3 deletions src/PowerAuth/Session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,13 @@ namespace powerAuth
}
if (nullptr != ec_public_key) {
// validate signature
success = crypto::ECDSA_ValidateSignature(data.data, data.signature, ec_public_key);
if (data.signatureFormat == SignedData::ECDSA_JOSE) {
// Convert signature from JOSE to DER first.
success = crypto::ECDSA_ValidateSignature(data.data, crypto::ECDSA_JOSEtoDER(data.signature), ec_public_key);
} else {
// No signature conversion required.
success = crypto::ECDSA_ValidateSignature(data.data, data.signature, ec_public_key);
}
//
} else {
CC7_LOG("Session %p: ServerSig: %s public key is invalid.", this, use_master_server_key ? "Master server" : "Server");
Expand Down Expand Up @@ -735,7 +741,7 @@ namespace powerAuth
}
cc7::ByteArray signing_key;
if (app_scope) {
signing_key = cc7::MakeRange(_setup.applicationSecret);
signing_key.readFromBase64String(_setup.applicationSecret);
} else {
// Unlock transport key
protocol::SignatureKeys plain;
Expand Down Expand Up @@ -904,7 +910,8 @@ namespace powerAuth
}

ErrorCode Session::signDataWithDevicePrivateKey(const std::string & c_vault_key, const SignatureUnlockKeys & keys,
const cc7::ByteRange & in_data, cc7::ByteArray & out_signature)
const cc7::ByteRange & in_data, SignedData::SignatureFormat out_format,
cc7::ByteArray & out_signature)
{
LOCK_GUARD();
cc7::ByteArray vault_key;
Expand Down Expand Up @@ -932,6 +939,13 @@ namespace powerAuth
// Signature calculation failed.
break;
}
if (out_format == SignedData::ECDSA_JOSE) {
out_signature = crypto::ECDSA_DERtoJOSE(out_signature);
if (out_signature.empty()) {
// Conversion to JOSE format failed.
break;
}
}
code = EC_Ok;
} while (false);

Expand Down
Loading

0 comments on commit d0191fc

Please sign in to comment.