Skip to content

Commit ae9d6d1

Browse files
authored
Creating CSR (#749)
1 parent 4e71b2b commit ae9d6d1

File tree

19 files changed

+717
-1
lines changed

19 files changed

+717
-1
lines changed

docs/PowerAuth-SDK-for-Android.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
- [Common SDK Tasks](#common-sdk-tasks)
3636
- [Additional Features](#additional-features)
3737
- [Obtaining User's Claims](#obtaining-users-claims)
38+
- [Creating Certificate Signing Request](#creating-certificate-signing-request)
3839
- [Password Strength Indicator](#password-strength-indicator)
3940
- [Debug Build Detection](#debug-build-detection)
4041
- [Request Interceptors](#request-interceptors)
@@ -2990,6 +2991,46 @@ If the `address` is provided, then `UserAddress` contains the following properti
29902991
Be aware that all properties in the `UserInfo` and `UserAddress` objects are optional and the availability of information depends on actual implementation on the server.
29912992
<!-- end -->
29922993

2994+
### Creating Certificate Signing Request
2995+
2996+
The PowerAuth SDK can create a Certificate Signing Request (CSR) that can be used to request an X.509 certificate from a Public Key Infrastructure (PKI).
2997+
The CSR contains a public key generated by the SDK and is signed with the activation-bound private key.
2998+
2999+
The created CSR in the PEM format (including `-----BEGIN CERTIFICATE REQUEST-----` and `-----END CERTIFICATE REQUEST-----` lines and newlines `\n`).
3000+
3001+
To create a CSR, use the following code:
3002+
3003+
```kotlin
3004+
// Assume this is your PowerAuthSDK instance
3005+
// The instance must have a valid and active activation
3006+
val powerAuth: PowerAuthSDK!!
3007+
3008+
val authentication = PowerAuthAuthentication.possessionWithPassword("1111") // assume this is your password
3009+
3010+
powerAuth.createSignedCSR(
3011+
appContext, // Android context
3012+
authentication, // authentication object
3013+
mapOf( // subject's distinguished names (DN)
3014+
Pair("CN", "wultra.com"),
3015+
Pair("O", "Wultra"),
3016+
Pair("C", "CZ")
3017+
),
3018+
arrayOf( // subject's alternative names (SAN)
3019+
"IP: 192.168.1.10",
3020+
"email: admin@example.com"
3021+
),
3022+
object : ICreateCSRListener {
3023+
override fun onCSRCreateSucceed(csr: String) {
3024+
Log.d("CSR", "CSR: ${csr}")
3025+
// Use the CSR
3026+
}
3027+
3028+
override fun onCSRCreateFailed(error: PowerAuthErrorException) {
3029+
// Handle error
3030+
}
3031+
}
3032+
)
3033+
```
29933034

29943035
### Password Strength Indicator
29953036

docs/PowerAuth-SDK-for-iOS.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
- [Common SDK Tasks](#common-sdk-tasks)
5050
- [Additional Features](#additional-features)
5151
- [Obtaining User's Claims](#obtaining-users-claims)
52+
- [Creating Certificate Signing Request](#creating-certificate-signing-request)
5253
- [Password Strength Indicator](#password-strength-indicator)
5354
- [Debug Build Detection](#debug-build-detection)
5455
- [Request Interceptors](#request-interceptors)
@@ -2240,6 +2241,45 @@ If the `address` is provided, then `PowerAuthUserAddress` contains the following
22402241
Be aware that all properties in the `PowerAuthUserInfo` and `PowerAuthUserAddress` objects are optional and the availability of information depends on actual implementation on the server.
22412242
<!-- end -->
22422243
2244+
### Creating Certificate Signing Request
2245+
2246+
The PowerAuth SDK can create a Certificate Signing Request (CSR) that can be used to request an X.509 certificate from a Public Key Infrastructure (PKI).
2247+
The CSR contains a public key generated by the SDK and is signed with the activation-bound private key.
2248+
2249+
The created CSR in the PEM format (including `-----BEGIN CERTIFICATE REQUEST-----` and `-----END CERTIFICATE REQUEST-----` lines and newlines `\n`).
2250+
2251+
To create a CSR, use the following code:
2252+
2253+
```swift
2254+
// Assume this is your PowerAuthSDK instance
2255+
// The instance must have a valid and active activation
2256+
let powerAuth: PowerAuthSDK!
2257+
2258+
let password = PowerAuthCorePassword(string: "1234") // assume this is your password
2259+
let authentication = PowerAuthAuthentication.possessionWithPassword(password: password)
2260+
2261+
powerAuth.createSignedCSR(
2262+
with: authentication, // authentication object
2263+
distinguishedNames: [ // subject's distinguished names (DN)
2264+
"CN": "wultra.com",
2265+
"O": "Wultra",
2266+
"C": "CZ"
2267+
],
2268+
subjectAltNames: [ // subject's alternative names (SAN)
2269+
"IP: 192.168.1.10",
2270+
"email: admin@example.com"
2271+
]
2272+
) { csr, error in
2273+
2274+
if let csr {
2275+
print("CSR: \(csr)")
2276+
// Use the CSR
2277+
} else {
2278+
// Handle error
2279+
}
2280+
}
2281+
```
2282+
22432283
### Password Strength Indicator
22442284
22452285
Choosing a weak passphrase in applications with high-security demands can be potentially dangerous. You can use our [Wultra Passphrase Meter](https://github.com/wultra/passphrase-meter) library to estimate the strength of the passphrase and warn the user when he tries to use such a passphrase in your application.

include/PowerAuth/Session.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,23 @@ namespace powerAuth
441441
ErrorCode decryptVaultKey(const std::string & c_vault_key, const SignatureUnlockKeys & keys,
442442
cc7::ByteArray & out_key);
443443

444+
public:
445+
446+
// MARK: - Certificate Signing Request -
447+
448+
/**
449+
Creates X.509 CSR (Certificate Signing Request) with given Distinguished Names and optional Subject Alternative Names, embedded device public key and signed with the device private key.
450+
451+
You have to provide at keys.userPassword and keys.possessionUnlockKey.
452+
453+
454+
Returns EC_Ok if operation succeeded
455+
EC_Encryption if general encryption error occurs
456+
EC_WrongState if the session has no valid activation
457+
EC_WrongParam if some required parameter is missing
458+
*/
459+
ErrorCode createPrivateKeySignedCSR(const std::string & c_vault_key, const SignatureUnlockKeys & keys, const std::map<std::string, std::string>& dn_items, const std::vector<std::string>& san_items, std::string &out_csr);
460+
444461
public:
445462

446463
// MARK: - External encryption key -
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
VERSION_NAME=1.9.5
1+
VERSION_NAME=1.9.6-SNAPSHOT
22
GROUP_ID=com.wultra.android.powerauth
33
ARTIFACT_ID=powerauth-sdk

proj-android/PowerAuthLibrary/src/main/java/io/getlime/security/powerauth/core/Session.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,46 @@ public byte[] prepareKeyValueDictionaryForDataSigning(Map<String, String> keyVal
483483
*/
484484
public native byte[] signDataWithDevicePrivateKey(String cVaultKey, SignatureUnlockKeys unlockKeys, byte[] data, @SignatureFormat int signatureFormat);
485485

486+
/**
487+
* Creates X.509 CSR (Certificate Signing Request) with given Distinguished Names and optional Subject Alternative Names, embedded device public key and signed with the device private key.
488+
* <p>
489+
* You have to provide encrypted vault key |cVaultKey| in Base64 format and |unlockKeys| object where the valid userPassword is set.
490+
*
491+
* <h2>Discussion</h2>
492+
*
493+
* The session's state contains device private key but it is encrypted with a vault key, which is normally not
494+
* available on the device. Just like other vault related operations, you have to properly sign HTTP request
495+
* with using PA2SignatureFactor_PrepareForVaultUnlock flag, otherwise the operation will fail.
496+
*
497+
* @param cVaultKey encrypted vault key
498+
* @param unlockKeys unlock keys object with required possession factor
499+
* @param distinguishedNames Distinguished Names (DN) to be embedded in the CSR.
500+
* @param subjectAltNames Subject Alternative Names (SAN)
501+
*
502+
* @return Returns CSR in PEM format or null in case of failure.
503+
*/
504+
public String createPrivateKeySignedCSR(
505+
@NonNull String cVaultKey,
506+
@NonNull SignatureUnlockKeys unlockKeys,
507+
@NonNull Map<String, String> distinguishedNames,
508+
@Nullable String[] subjectAltNames) {
509+
510+
ArrayList<String> dnKeys = new ArrayList<>();
511+
ArrayList<String> dnValues = new ArrayList<>();
512+
for (Map.Entry<String, String> entry : distinguishedNames.entrySet()) {
513+
dnKeys.add(entry.getKey());
514+
dnValues.add(entry.getValue());
515+
}
516+
return createPrivateKeySignedCSR(cVaultKey, unlockKeys, dnKeys.toArray(new String[0]), dnValues.toArray(new String[0]), subjectAltNames);
517+
}
518+
519+
private native String createPrivateKeySignedCSR(
520+
@NonNull String cVaultKey,
521+
@NonNull SignatureUnlockKeys unlockKeys,
522+
@Nullable String[] dnKeys,
523+
@Nullable String[] dnValues,
524+
@Nullable String[] subjectAltNames);
525+
486526
//
487527
// External encryption key
488528
//
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2025 Wultra s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.getlime.security.powerauth.networking.response;
18+
19+
import androidx.annotation.MainThread;
20+
import androidx.annotation.NonNull;
21+
22+
import io.getlime.security.powerauth.exception.PowerAuthErrorException;
23+
24+
/**
25+
* Listener for Creating PowerAuth signed CSR.
26+
*/
27+
public interface ICreateCSRListener {
28+
29+
/**
30+
* When CSR is successfully created, this method is called with CSR string in PEM format.
31+
*/
32+
@MainThread
33+
void onCSRCreateSucceed(@NonNull String csr);
34+
35+
/**
36+
* Called when CSR creation fails.
37+
*
38+
* @param error Error that occurred during the CSR creation.
39+
*/
40+
@MainThread
41+
void onCSRCreateFailed(@NonNull PowerAuthErrorException error);
42+
}

proj-android/PowerAuthLibrary/src/main/java/io/getlime/security/powerauth/sdk/PowerAuthSDK.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2348,6 +2348,63 @@ public void onDataSignedFailed(@NonNull Throwable t) {
23482348
});
23492349
}
23502350

2351+
/**
2352+
* Creates X.509 CSR (Certificate Signing Request) with given Distinguished Names and optional Subject Alternative Names, embedded device public key and signed with the device private key.
2353+
* <p>
2354+
* This method calls PowerAuth Standard RESTful API endpoint '/pa/vault/unlock' to obtain the vault encryption key used for private recovery data decryption.
2355+
*
2356+
* @param context Android context.
2357+
* @param authentication Authentication object that must contain the possession and password factor.
2358+
* @param distinguishedNames Distinguished Names (DN) to be embedded in the CSR. The dictionary keys are DN types (like "CN", "O", etc.) and values are corresponding DN values.
2359+
* @param subjectAltNames Optional array of Subject Alternative Names (SAN)
2360+
* @param listener Listener with the callback methods. CSR in PEM format with lines separated by `\n` (including `-----BEGIN CERTIFICATE REQUEST`----- and `-----END CERTIFICATE REQUEST-----` lines) is returned in case of success.
2361+
* @return {@link ICancelable} object associated with the underlying HTTP request.
2362+
*/
2363+
@Nullable
2364+
public ICancelable createSignedCSR(
2365+
@NonNull Context context,
2366+
@NonNull PowerAuthAuthentication authentication,
2367+
@NonNull Map<String, String> distinguishedNames,
2368+
@Nullable String[] subjectAltNames,
2369+
@NonNull ICreateCSRListener listener) {
2370+
// Fetch vault unlock key
2371+
final CompositeCancelableTask compositeCancelableTask = new CompositeCancelableTask(true);
2372+
2373+
final ICancelable httpRequest = fetchEncryptedVaultUnlockKey(context, authentication, VaultUnlockReason.SIGN_WITH_DEVICE_PRIVATE_KEY, new IFetchEncryptedVaultUnlockKeyListener() {
2374+
2375+
@Override
2376+
public void onFetchEncryptedVaultUnlockKeySucceed(final String encryptedEncryptionKey) {
2377+
if (encryptedEncryptionKey != null) {
2378+
SignatureUnlockKeys keys = new SignatureUnlockKeys(deviceRelatedKey(context), null, authentication.getPassword());
2379+
String csr = mSession.createPrivateKeySignedCSR(encryptedEncryptionKey, keys, distinguishedNames, subjectAltNames);
2380+
if (compositeCancelableTask.setCompleted()) {
2381+
if (csr != null) {
2382+
listener.onCSRCreateSucceed(csr);
2383+
} else {
2384+
listener.onCSRCreateFailed(new PowerAuthErrorException(PowerAuthErrorCodes.SIGNATURE_ERROR));
2385+
}
2386+
}
2387+
} else {
2388+
if (compositeCancelableTask.setCompleted()) {
2389+
listener.onCSRCreateFailed(new PowerAuthErrorException(PowerAuthErrorCodes.SIGNATURE_ERROR));
2390+
}
2391+
}
2392+
}
2393+
2394+
@Override
2395+
public void onFetchEncryptedVaultUnlockKeyFailed(Throwable t) {
2396+
if (compositeCancelableTask.setCompleted()) {
2397+
listener.onCSRCreateFailed(PowerAuthErrorException.wrapException(PowerAuthErrorCodes.NETWORK_ERROR, t));
2398+
}
2399+
}
2400+
});
2401+
if (httpRequest != null) {
2402+
compositeCancelableTask.addCancelable(httpRequest);
2403+
return compositeCancelableTask;
2404+
}
2405+
return null;
2406+
}
2407+
23512408
// E2EE
23522409

23532410
/**

proj-xcode/PowerAuth2/PowerAuthSDK.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,21 @@
550550
claims:(nonnull NSDictionary<NSString*, NSObject*>*)claims
551551
callback:(nonnull void(^)(NSString * _Nullable jwt, NSError * _Nullable error))callback;
552552

553+
/** Creates X.509 CSR (Certificate Signing Request) with given Distinguished Names and optional Subject Alternative Names, embedded device public key and signed with the device private key.
554+
555+
This method calls PowerAuth Standard RESTful API endpoint '/pa/vault/unlock' to obtain the vault encryption key used for private recovery data decryption.
556+
557+
@param authentication Authentication used for vault unlocking call.
558+
@param distinguishedNames Distinguished Names (DN) to be embedded in the CSR. The dictionary keys are DN types (like "CN", "O", etc.) and values are corresponding DN values.
559+
@param subjectAltNames Optional array of Subject Alternative Names (SAN)
560+
@param callback The callback method with the CSR in PEM format with lines separated by `\n` (including `-----BEGIN CERTIFICATE REQUEST`----- and `-----END CERTIFICATE REQUEST-----` lines).
561+
@return PowerAuthOperationTask associated with the running request.
562+
*/
563+
- (nullable id<PowerAuthOperationTask>) createSignedCSRWithAuthentication:(nonnull PowerAuthAuthentication*)authentication
564+
distinguishedNames:(nonnull NSDictionary<NSString*, NSString*>*)distinguishedNames
565+
subjectAltNames:(nullable NSArray<NSString*>*)subjectAltNames
566+
callback:(nonnull void(^)(NSString * _Nullable csr, NSError * _Nullable error))callback;
567+
553568
@end
554569

555570

proj-xcode/PowerAuth2/PowerAuthSDK.m

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,6 +1523,35 @@ - (NSData*) generateInvalidBiometricKey
15231523
}];
15241524
}
15251525

1526+
- (nullable id<PowerAuthOperationTask>) createSignedCSRWithAuthentication:(PowerAuthAuthentication*)authentication
1527+
distinguishedNames:(NSDictionary<NSString*, NSString*>*)distinguishedNames
1528+
subjectAltNames:(NSArray<NSString*>*)subjectAltNames
1529+
callback:(void(^)(NSString * csr, NSError * error))callback
1530+
{
1531+
if (!distinguishedNames) {
1532+
callback(nil, PA2MakeError(PowerAuthErrorCode_WrongParameter, @"Distinguished names are missing"));
1533+
return nil;
1534+
}
1535+
return [self fetchEncryptedVaultUnlockKey:authentication reason:PA2VaultUnlockReason_SIGN_WITH_DEVICE_PRIVATE_KEY callback:^(NSString *encryptedEncryptionKey, NSError *error) {
1536+
NSString *csr = nil;
1537+
if (!error) {
1538+
// Let's create the CSR
1539+
PowerAuthCoreSignatureUnlockKeys *keys = [[PowerAuthCoreSignatureUnlockKeys alloc] init];
1540+
keys.possessionUnlockKey = [self deviceRelatedKey];
1541+
keys.userPassword = authentication.password;
1542+
csr = [_sessionInterface readTaskWithSession:^id (PowerAuthCoreSession * session) {
1543+
return [session createPrivateKeySignedCSR:encryptedEncryptionKey keys:keys distinguishedNames:distinguishedNames subjectAltNames:subjectAltNames];
1544+
}];
1545+
// Propagate error
1546+
if (!csr) {
1547+
error = PA2MakeError(PowerAuthErrorCode_SignatureError, @"Failed to create CSR");
1548+
}
1549+
}
1550+
// Call back to application
1551+
callback(csr, error);
1552+
}];
1553+
}
1554+
15261555
@end
15271556

15281557
#pragma mark - End-2-End Encryption

proj-xcode/PowerAuthCore.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@
171171
BFFE1D57264D688F00D5B985 /* pa2CryptoECDSATests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BFFE1D51264D688F00D5B985 /* pa2CryptoECDSATests.cpp */; };
172172
BFFE1D58264D688F00D5B985 /* pa2CryptoECCTests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BFFE1D55264D688F00D5B985 /* pa2CryptoECCTests.cpp */; };
173173
BFFE1D59264D688F00D5B985 /* pa2CryptoECCTests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BFFE1D55264D688F00D5B985 /* pa2CryptoECCTests.cpp */; };
174+
DCE5FEC92E687C8A00ED8C32 /* CSR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DCE5FEC82E687C8A00ED8C32 /* CSR.cpp */; };
175+
DCE5FECA2E687C8A00ED8C32 /* CSR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DCE5FEC82E687C8A00ED8C32 /* CSR.cpp */; };
174176
/* End PBXBuildFile section */
175177

176178
/* Begin PBXContainerItemProxy section */
@@ -492,6 +494,8 @@
492494
BFEEEBEA2A3B067B007C6737 /* pa2ByteUtilsTests.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = pa2ByteUtilsTests.cpp; sourceTree = "<group>"; };
493495
BFFE1D51264D688F00D5B985 /* pa2CryptoECDSATests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pa2CryptoECDSATests.cpp; sourceTree = "<group>"; };
494496
BFFE1D55264D688F00D5B985 /* pa2CryptoECCTests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pa2CryptoECCTests.cpp; sourceTree = "<group>"; };
497+
DCE5FEB82E68626900ED8C32 /* CSR.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSR.h; sourceTree = "<group>"; };
498+
DCE5FEC82E687C8A00ED8C32 /* CSR.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CSR.cpp; sourceTree = "<group>"; };
495499
/* End PBXFileReference section */
496500

497501
/* Begin PBXFrameworksBuildPhase section */
@@ -762,6 +766,8 @@
762766
BF99D8D52073E00D00735ED2 /* KDF.cpp */,
763767
BF99D8DE2073E00D00735ED2 /* MAC.h */,
764768
BF99D8DB2073E00D00735ED2 /* MAC.cpp */,
769+
DCE5FEB82E68626900ED8C32 /* CSR.h */,
770+
DCE5FEC82E687C8A00ED8C32 /* CSR.cpp */,
765771
);
766772
path = crypto;
767773
sourceTree = "<group>";
@@ -1286,6 +1292,7 @@
12861292
BF99D90F2073E15100735ED2 /* KDF.cpp in Sources */,
12871293
BF99D9022073E14100735ED2 /* Debug.cpp in Sources */,
12881294
BFEEEBE12A3ADEA8007C6737 /* ByteUtils.cpp in Sources */,
1295+
DCE5FEC92E687C8A00ED8C32 /* CSR.cpp in Sources */,
12891296
BFB47D1620753324008A6A52 /* DataReader.cpp in Sources */,
12901297
BF99D9092073E14700735ED2 /* ProtocolUtils.cpp in Sources */,
12911298
);
@@ -1349,6 +1356,7 @@
13491356
BF6ADD7C24C84C0C001B3E5E /* KDF.cpp in Sources */,
13501357
BF6ADD7D24C84C0C001B3E5E /* Debug.cpp in Sources */,
13511358
BFEEEBE22A3ADEA8007C6737 /* ByteUtils.cpp in Sources */,
1359+
DCE5FECA2E687C8A00ED8C32 /* CSR.cpp in Sources */,
13521360
BF6ADD7E24C84C0C001B3E5E /* DataReader.cpp in Sources */,
13531361
BF6ADD7F24C84C0C001B3E5E /* ProtocolUtils.cpp in Sources */,
13541362
);

0 commit comments

Comments
 (0)