Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce new APIs to read EC private keys and public keys #533

Merged
merged 8 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from 7 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
6 changes: 3 additions & 3 deletions ballerina/Ballerina.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
org = "ballerina"
name = "crypto"
version = "2.5.0"
version = "2.6.0"
authors = ["Ballerina"]
keywords = ["security", "hash", "hmac", "sign", "encrypt", "decrypt", "private key", "public key"]
repository = "https://github.com/ballerina-platform/module-ballerina-crypto"
Expand All @@ -15,8 +15,8 @@ graalvmCompatible = true
[[platform.java17.dependency]]
groupId = "io.ballerina.stdlib"
artifactId = "crypto-native"
version = "2.5.0"
path = "../native/build/libs/crypto-native-2.5.0.jar"
version = "2.6.0"
path = "../native/build/libs/crypto-native-2.6.0-SNAPSHOT.jar"

[[platform.java17.dependency]]
groupId = "org.bouncycastle"
Expand Down
2 changes: 1 addition & 1 deletion ballerina/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ distribution-version = "2201.8.0"
[[package]]
org = "ballerina"
name = "crypto"
version = "2.5.0"
version = "2.6.0"
dependencies = [
{org = "ballerina", name = "jballerina.java"},
{org = "ballerina", name = "test"},
Expand Down
35 changes: 35 additions & 0 deletions ballerina/private_public_key.bal
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,24 @@ public isolated function decodeRsaPrivateKeyFromKeyStore(KeyStore keyStore, stri
'class: "io.ballerina.stdlib.crypto.nativeimpl.Decode"
} external;

# Decodes the EC private key from the given PKCS#12 archive file.
# ```ballerina
# crypto:KeyStore keyStore = {
# path: "/path/to/keyStore.p12",
# password: "keyStorePassword"
# };
# crypto:PrivateKey privateKey = check crypto:decodeEcPrivateKeyFromKeyStore(keyStore, "keyAlias", "keyPassword");
# ```
#
# + keyStore - KeyStore configurations
# + keyAlias - Key alias
# + keyPassword - Key password
# + return - Reference to the private key or else a `crypto:Error` if the private key was unreadable
public isolated function decodeEcPrivateKeyFromKeyStore(KeyStore keyStore, string keyAlias, string keyPassword)
returns PrivateKey|Error = @java:Method {
'class: "io.ballerina.stdlib.crypto.nativeimpl.Decode"
} external;

# Decodes the RSA private key from the given private key and private key password.
# ```ballerina
# string keyFile = "/path/to/private.key";
Expand Down Expand Up @@ -127,6 +145,23 @@ public isolated function decodeRsaPublicKeyFromTrustStore(TrustStore trustStore,
'class: "io.ballerina.stdlib.crypto.nativeimpl.Decode"
} external;

# Decodes the EC public key from the given PKCS#12 archive file.
# ```ballerina
# crypto:TrustStore trustStore = {
# path: "/path/tp/truststore.p12",
# password: "truststorePassword"
# };
# crypto:PublicKey publicKey = check crypto:decodeEcPublicKeyFromTrustStore(trustStore, "keyAlias");
# ```
#
# + trustStore - TrustStore configurations
# + keyAlias - Key alias
# + return - Reference to the public key or else a `crypto:Error` if the public key was unreadable
public isolated function decodeEcPublicKeyFromTrustStore(TrustStore trustStore, string keyAlias)
returns PublicKey|Error = @java:Method {
'class: "io.ballerina.stdlib.crypto.nativeimpl.Decode"
} external;

# Decodes the RSA public key from the given public certificate file.
# ```ballerina
# string certFile = "/path/to/public.cert";
Expand Down
44 changes: 44 additions & 0 deletions ballerina/sign_verify.bal
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,26 @@ public isolated function signRsaSha512(byte[] input, PrivateKey privateKey) retu
'class: "io.ballerina.stdlib.crypto.nativeimpl.Sign"
} external;

# Returns the SHA384withECDSA based signature value for the given data.
# ```ballerina
# string input = "Hello Ballerina";
# byte[] data = input.toBytes();
# crypto:KeyStore keyStore = {
# path: "/path/to/keyStore.p12",
# password: "keyStorePassword"
# };
# crypto:PrivateKey privateKey = check crypto:decodeEcPrivateKeyFromKeyStore(keyStore, "keyAlias", "keyPassword");
# byte[] signature = check crypto:signSha384withEcdsa(data, privateKey);
# ```
#
# + input - The content to be signed
# + privateKey - Private key used for signing
# + return - The generated signature or else a `crypto:Error` if the private key is invalid
public isolated function signSha384withEcdsa(byte[] input, PrivateKey privateKey) returns byte[]|Error = @java:Method {
name: "signSha384withEcdsa",
'class: "io.ballerina.stdlib.crypto.nativeimpl.Sign"
} external;

# Verifies the RSA-MD5 based signature.
# ```ballerina
# string input = "Hello Ballerina";
Expand Down Expand Up @@ -235,3 +255,27 @@ public isolated function verifyRsaSha512Signature(byte[] data, byte[] signature,
name: "verifyRsaSha512Signature",
'class: "io.ballerina.stdlib.crypto.nativeimpl.Sign"
} external;

# Verifies the SHA384withECDSA based signature.
# ```ballerina
# string input = "Hello Ballerina";
# byte[] data = input.toBytes();
# crypto:KeyStore keyStore = {
# path: "/path/to/keyStore.p12",
# password: "keyStorePassword"
# };
# crypto:PrivateKey privateKey = check crypto:decodeEcPrivateKeyFromKeyStore(keyStore, "keyAlias", "keyPassword");
# byte[] signature = check crypto:signSha384withEcdsa(data, privateKey);
# crypto:PublicKey publicKey = check crypto:decodeEcPublicKeyFromTrustStore(keyStore, "keyAlias");
# boolean validity = check crypto:verifySha384withEcdsaSignature(data, signature, publicKey);
# ```
#
# + data - The content to be verified
# + signature - Signature value
# + publicKey - Public key used for verification
# + return - Validity of the signature or else a `crypto:Error` if the public key is invalid
public isolated function verifySha384withEcdsaSignature(byte[] data, byte[] signature, PublicKey publicKey)
returns boolean|Error = @java:Method {
name: "verifySha384withEcdsaSignature",
'class: "io.ballerina.stdlib.crypto.nativeimpl.Sign"
} external;
Binary file added ballerina/tests/resources/ec-keystore.pkcs12
Binary file not shown.
69 changes: 69 additions & 0 deletions ballerina/tests/sign_verify_test.bal
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,72 @@ isolated function testVerifyRsaSha512() returns Error? {
byte[] sha512Signature = check signRsaSha512(payload, privateKey);
test:assertTrue(check verifyRsaSha512Signature(payload, sha512Signature, publicKey));
}

@test:Config {}
isolated function testVerifySha384withEcdsa() returns Error? {
byte[] payload = "Ballerina test".toBytes();
KeyStore keyStore = {
path: EC_KEYSTORE_PATH,
password: "ballerina"
};
PrivateKey privateKey = check decodeEcPrivateKeyFromKeyStore(keyStore, "ec-keypair", "ballerina");
PublicKey publicKey = check decodeEcPublicKeyFromTrustStore(keyStore, "ec-keypair");
byte[] sha384withEcdsaSignature = check signSha384withEcdsa(payload, privateKey);
test:assertTrue(check verifySha384withEcdsaSignature(payload, sha384withEcdsaSignature, publicKey));
}

@test:Config {}
isolated function testDecodeRsaPrivateKeyError() returns Error? {
KeyStore keyStore = {
path: EC_KEYSTORE_PATH,
password: "ballerina"
};
PrivateKey|Error privateKey = decodeRsaPrivateKeyFromKeyStore(keyStore, "ec-keypair", "ballerina");
if privateKey is Error {
test:assertEquals(privateKey.message(), "Not a valid RSA key");
} else {
test:assertFail("Expected error not found");
}
}

@test:Config {}
isolated function testDecodeEcPrivateKeyError() returns Error? {
KeyStore keyStore = {
path: KEYSTORE_PATH,
password: "ballerina"
};
PrivateKey|Error privateKey = decodeEcPrivateKeyFromKeyStore(keyStore, "ballerina", "ballerina");
if privateKey is Error {
test:assertEquals(privateKey.message(), "Not a valid EC key");
} else {
test:assertFail("Expected error not found");
}
}

@test:Config {}
isolated function testDecodeEcPublicKeyError() returns Error? {
KeyStore keyStore = {
path: KEYSTORE_PATH,
password: "ballerina"
};
PublicKey|Error publicKey = decodeEcPublicKeyFromTrustStore(keyStore, "ballerina");
if publicKey is Error {
test:assertEquals(publicKey.message(), "Not a valid EC public key");
} else {
test:assertFail("Expected error not found");
}
}

@test:Config {}
isolated function testDecodeRsaPublicKeyError() returns Error? {
KeyStore keyStore = {
path: EC_KEYSTORE_PATH,
password: "ballerina"
};
PublicKey|Error publicKey = decodeRsaPublicKeyFromTrustStore(keyStore, "ec-keypair");
if publicKey is Error {
test:assertEquals(publicKey.message(), "Not a valid RSA public key");
} else {
test:assertFail("Expected error not found");
}
}
1 change: 1 addition & 0 deletions ballerina/tests/test_utils.bal
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// under the License.

const string KEYSTORE_PATH = "tests/resources/keyStore.p12";
const string EC_KEYSTORE_PATH = "tests/resources/ec-keystore.pkcs12";
const string ENCRYPTED_KEY_PAIR_PATH = "tests/resources/encryptedKeyPair.pem";
const string KEY_PAIR_PATH = "tests/resources/keyPair.pem";
const string ENCRYPTED_PRIVATE_KEY_PATH = "tests/resources/encryptedPrivate.key";
Expand Down
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Added
- [Introduce new APIs to interact with EC private keys and public keys](https://github.com/ballerina-platform/ballerina-library/issues/5821)

## [2.5.0] - 2023-09-15

### Changed
Expand Down
34 changes: 34 additions & 0 deletions docs/spec/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,14 @@ The conforming implementation of the specification is released and included in t
* 6.1.3. [RSA-SHA256](#613-rsa-sha256)
* 6.1.4. [RSA-SHA384](#614-rsa-sha384)
* 6.1.5. [RSA-SHA512](#615-rsa-sha512)
* 6.1.6. [SHA384withECDSA](#616-sha384withecdsa)
* 6.2. [Verify signature](#62-verify-signature)
* 6.2.1. [RSA-MD5](#621-rsa-md5)
* 6.2.2. [RSA-SHA1](#622-rsa-sha1)
* 6.2.3. [RSA-SHA256](#623-rsa-sha256)
* 6.2.4. [RSA-SHA384](#624-rsa-sha384)
* 6.2.5. [RSA-SHA512](#625-rsa-sha512)
* 6.2.6. [SHA384withECDSA](#626-sha384withecdsa)

## 1. [Overview](#1-overview)

Expand Down Expand Up @@ -471,6 +473,21 @@ crypto:PrivateKey privateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(keyS
byte[] signature = check crypto:signRsaSha512(data, privateKey);
```

#### 6.1.6. [SHA384withECDSA](#616-sha384withecdsa)

This API can be used to create the SHA384withECDSA based signature value for the given data.

```ballerina
string input = "Hello Ballerina";
byte[] data = input.toBytes();
crypto:KeyStore keyStore = {
path: "/path/to/keyStore.p12",
password: "keyStorePassword"
};
crypto:PrivateKey privateKey = check crypto:decodeEcPrivateKeyFromKeyStore(keyStore, "keyAlias", "keyPassword");
byte[] signature = check crypto:signSha384withEcdsa(data, privateKey);
```

### 6.2. [Verify signature](#62-verify-signature)

#### 6.2.1. [RSA-MD5](#621-rsa-md5)
Expand Down Expand Up @@ -557,3 +574,20 @@ byte[] signature = check crypto:signRsaSha512(data, privateKey);
crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromTrustStore(keyStore, "keyAlias");
boolean validity = check crypto:verifyRsaSha512Signature(data, signature, publicKey);
```

#### 6.2.6. [SHA384withECDSA](#626-sha384withecdsa)

This API can be used to verify the SHA384withECDSA based signature.

```ballerina
string input = "Hello Ballerina";
byte[] data = input.toBytes();
crypto:KeyStore keyStore = {
path: "/path/to/keyStore.p12",
password: "keyStorePassword"
};
crypto:PrivateKey privateKey = check crypto:decodeEcPrivateKeyFromKeyStore(keyStore, "keyAlias", "keyPassword");
byte[] signature = check crypto:signSha384withEcdsa(data, privateKey);
crypto:PublicKey publicKey = check crypto:decodeEcPublicKeyFromTrustStore(keyStore, "keyAlias");
boolean validity = check crypto:verifySha384withEcdsaSignature(data, signature, publicKey);
```
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
org.gradle.caching=true
group=io.ballerina.stdlib
version=2.5.1-SNAPSHOT
version=2.6.0-SNAPSHOT
puppycrawlCheckstyleVersion=10.12.0
bouncycastleVersion=1.74
githubSpotbugsVersion=5.0.14
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ private Constants() {}
// Native data key for private key within the PrivateKey record.
public static final String NATIVE_DATA_PRIVATE_KEY = "NATIVE_DATA_PRIVATE_KEY";

public static final String NATIVE_DATA_EC_PRIVATE_KEY = "NATIVE_DATA_EC_PRIVATE_KEY";

// Native data key for private key within the PublicKey record.
public static final String NATIVE_DATA_PUBLIC_KEY = "NATIVE_DATA_PUBLIC_KEY";

Expand Down Expand Up @@ -97,6 +99,9 @@ private Constants() {}
// RSA key algorithm
public static final String RSA_ALGORITHM = "RSA";

// EC key algorithm
public static final String EC_ALGORITHM = "EC";

// GMT timezone name used for X509 validity times
public static final String TIMEZONE_GMT = "GMT";

Expand Down
Loading
Loading