Skip to content

Commit

Permalink
Add new APIs to read EC public and private keys from files
Browse files Browse the repository at this point in the history
  • Loading branch information
Bhashinee committed Dec 11, 2023
1 parent 6f1b573 commit dd15ed6
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 7 deletions.
26 changes: 26 additions & 0 deletions ballerina/private_public_key.bal
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,20 @@ public isolated function decodeRsaPrivateKeyFromKeyFile(string keyFile, string?
'class: "io.ballerina.stdlib.crypto.nativeimpl.Decode"
} external;

# Decodes the EC private key from the given private key and private key password.
# ```ballerina
# string keyFile = "/path/to/private.key";
# crypto:PrivateKey privateKey = check crypto:decodeEcPrivateKeyFromKeyFile(keyFile, "keyPassword");
# ```
#
# + keyFile - Path to the key file
# + keyPassword - Password of the key file if it is encrypted
# + return - Reference to the private key or else a `crypto:Error` if the private key was unreadable
public isolated function decodeEcPrivateKeyFromKeyFile(string keyFile, string? keyPassword = ())
returns PrivateKey|Error = @java:Method {
'class: "io.ballerina.stdlib.crypto.nativeimpl.Decode"
} external;

# Decodes the RSA public key from the given PKCS#12 archive file.
# ```ballerina
# crypto:TrustStore trustStore = {
Expand Down Expand Up @@ -174,6 +188,18 @@ public isolated function decodeRsaPublicKeyFromCertFile(string certFile) returns
'class: "io.ballerina.stdlib.crypto.nativeimpl.Decode"
} external;

# Decodes the EC public key from the given public certificate file.
# ```ballerina
# string certFile = "/path/to/public.cert";
# crypto:PublicKey publicKey = check crypto:decodeEcPublicKeyFromCertFile(certFile);
# ```
#
# + certFile - Path to the certificate file
# + return - Reference to the public key or else a `crypto:Error` if the public key was unreadable
public isolated function decodeEcPublicKeyFromCertFile(string certFile) returns PublicKey|Error = @java:Method {
'class: "io.ballerina.stdlib.crypto.nativeimpl.Decode"
} external;

# Builds the RSA public key from the given modulus and exponent parameters.
# ```ballerina
# string modulus = "luZFdW1ynitztkWLC6xKegbRWxky...";
Expand Down
22 changes: 22 additions & 0 deletions ballerina/tests/private_public_key_test.bal
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ isolated function testParsePrivateKeyFromKeyPairFile() returns Error? {
test:assertEquals(result["algorithm"], "RSA");
}

@test:Config {}
isolated function testParseEcPrivateKeyFromKeyFile() returns Error? {
PrivateKey result = check decodeEcPrivateKeyFromKeyFile(EC_PRIVATE_KEY_PATH);
test:assertEquals(result["algorithm"], "ECDSA");
}

@test:Config {}
isolated function testReadPrivateKeyFromNonExistingKeyFile() {
PrivateKey|Error result = decodeRsaPrivateKeyFromKeyFile(INVALID_PRIVATE_KEY_PATH);
Expand Down Expand Up @@ -236,6 +242,22 @@ isolated function testParsePublicKeyFromX509CertFile() returns Error? {
test:assertEquals(signingAlgorithm, "SHA256withRSA");
}

@test:Config {}
isolated function testParseEcPublicKeyFromX509CertFile() returns Error? {
PublicKey publicKey = check decodeEcPublicKeyFromCertFile(EC_CERT_PATH);
test:assertEquals(publicKey["algorithm"], "EC");
Certificate certificate = <Certificate>publicKey["certificate"];

string serial = (<int>certificate["serial"]).toString();
string issuer = <string>certificate["issuer"];
string subject = <string>certificate["subject"];
string signingAlgorithm = <string>certificate["signingAlgorithm"];

test:assertEquals(serial, "813081972327485475");
test:assertEquals(issuer, "CN=sigstore-intermediate,O=sigstore.dev");
test:assertEquals(signingAlgorithm, "SHA384withECDSA");
}

@test:Config {}
isolated function testReadPublicKeyFromNonExistingCertFile() {
PublicKey|Error result = decodeRsaPublicKeyFromCertFile(INVALID_PUBLIC_CERT_PATH);
Expand Down
18 changes: 18 additions & 0 deletions ballerina/tests/resources/ec-cert.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIICzzCCAlWgAwIBAgIUJaBYA4gOhyDrjigfC0ilvvXakCMwCgYIKoZIzj0EAwMw
NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl
cm1lZGlhdGUwHhcNMjMxMjA4MDYxODI0WhcNMjMxMjA4MDYyODI0WjAAMFkwEwYH
KoZIzj0CAQYIKoZIzj0DAQcDQgAEyjXIb66skPFKbH8Bmjdg1DqZ6eOJV3za17Zs
EYpEgT2p33lzkiC2K9X39cATWrT1vd+PpzkRa6RrDobjrfzggqOCAXQwggFwMA4G
A1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUQik2
yPY3ziieNIhK2bADm1bRpmYwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y
ZD8wJAYDVR0RAQH/BBowGIEWZHdzbnNld3dhbmRpQGdtYWlsLmNvbTApBgorBgEE
AYO/MAEBBBtodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20wKwYKKwYBBAGDvzAB
CAQdDBtodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20wgYoGCisGAQQB1nkCBAIE
fAR6AHgAdgDdPTBqxscRMmMZHhyZZzcCokpeuN48rf+HinKALynujgAAAYxIE8WD
AAAEAwBHMEUCIQCWhoJcbSAxit5NiPMrze0N0JdQC/dmVu/EpKkCiT4Y4gIgBazA
hpv2Oq49TvaYINlfO86ziKcTEAP9uh93JlWXHvowCgYIKoZIzj0EAwMDaAAwZQIw
Nq1+EWmcWn4IcHOYe0QJaikbSXxRn6wC16Kn6M/BMqgH1I8MmnY46MeQozY+FyCO
AjEAz0WV59exLU1hhMhtCOHG7WNyC2/vt+EMeUxM2tkNtQk/rHjEhorkwiqlgzAT
4jel
-----END CERTIFICATE-----
2 changes: 2 additions & 0 deletions ballerina/tests/test_utils.bal
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const string KEY_PAIR_PATH = "tests/resources/keyPair.pem";
const string ENCRYPTED_PRIVATE_KEY_PATH = "tests/resources/encryptedPrivate.key";
const string PRIVATE_KEY_PATH = "tests/resources/private.key";
const string X509_PUBLIC_CERT_PATH = "tests/resources/public.crt";
const string EC_CERT_PATH = "tests/resources/ec-cert.crt";
const string EC_PRIVATE_KEY_PATH = "tests/resources/ec-key.pem";

const string INVALID_KEYSTORE_PATH = "tests/resources/cert/keyStore.p12.invalid";
const string INVALID_PRIVATE_KEY_PATH = "tests/resources/cert/private.key.invalid";
Expand Down
5 changes: 5 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Added
- [Introduce new APIs to decode private and public keys from files](https://github.com/ballerina-platform/ballerina-library/issues/5871)

## [2.6.0] - 2023-12-08

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

Expand Down
56 changes: 51 additions & 5 deletions docs/spec/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,15 @@ The conforming implementation of the specification is released and included in t
* 3.4. [SHA384](#34-sha384)
* 3.5. [SHA512](#35-sha512)
4. [Decode private/public key](#4-decode-private-public-keys)
* 4.1. [Decode Private key from PKCS12 file](#41-decode-private-key-from-pkcs12-file)
* 4.1. [Decode RSA Private key from PKCS12 file](#41-rsa-decode-private-key-from-pkcs12-file)
* 4.2. [Decode RSA Private key using Private key and Password](#42-decode-rsa-private-key-using-private-key-and-password)
* 4.3. [Decode RSA Public key from PKCS12 file](#43-decode-rsa-public-key-from-pkcs12-file)
* 4.4. [Decode RSA Public key from the certificate file](#44-decode-rsa-public-key-from-the-certificate-file)
* 4.5. [Build RSA Public key from modulus and exponent parameters](#45-build-rsa-public-key-from-modulus-and-exponent-parameters)
* 4.5. [Decode EC Private key from PKCS12 file](#45-decode-ec-private-key-from-pkcs12-file)
* 4.6. [Decode EC Private key using Private key and Password](#46-decode-ec-private-key-using-private-key-and-password)
* 4.7. [Decode EC Public key from PKCS12 file](#47-decode-ec-public-key-from-pkcs12-file)
* 4.8. [Decode EC Public key from the certificate file](#48-decode-ec-public-key-from-the-certificate-file)
* 4.9. [Build RSA Public key from modulus and exponent parameters](#45-build-rsa-public-key-from-modulus-and-exponent-parameters)
5. [Encrypt-Decrypt](#5-encrypt-decrypt)
* 5.1. [Encryption](#51-encryption)
* 5.1.1. [RSA](#511-rsa)
Expand Down Expand Up @@ -194,9 +198,9 @@ byte[] hmac = check crypto:hmacSha512(data, key);

The `crypto` library supports decoding the RSA private key from a `.p12` file and a key file in the `PEM` format. Also, it supports decoding a public key from a `.p12` file and a certificate file in the `X509` format. Additionally, this supports building an RSA public key with the modulus and exponent parameters.

### 4.1. [Decode Private key from PKCS12 file](#41-decode-private-key-from-pkcs12-file)
### 4.1. [Decode RSA Private key from PKCS12 file](#41-rsa-decode-private-key-from-pkcs12-file)

This API can be used to decode the private key from the given PKCS#12 file.
This API can be used to decode the RSA private key from the given PKCS#12 file.

```ballerina
crypto:KeyStore keyStore = {
Expand Down Expand Up @@ -236,7 +240,49 @@ string certFile = "/path/to/public.cert";
crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromCertFile(certFile);
```

### 4.5. [Build RSA Public key from modulus and exponent parameters](#45-build-rsa-public-key-from-modulus-and-exponent-parameters)
### 4.5. [Decode EC Private key from PKCS12 file](#45-decode-ec-private-key-from-pkcs12-file)

This API can be used to decode the EC private key from the given PKCS#12 file.

```ballerina
crypto:KeyStore keyStore = {
path: "/path/to/keyStore.p12",
password: "keyStorePassword"
};
crypto:PrivateKey privateKey = check crypto:decodeEcPrivateKeyFromKeyStore(keyStore, "keyAlias", "keyPassword");
```

### 4.6. [Decode EC Private key using Private key and Password](#46-decode-ec-private-key-using-private-key-and-password)

This API can be used to decode the EC private key from the given private key and private key password.

```ballerina
string keyFile = "/path/to/private.key";
crypto:PrivateKey privateKey = check crypto:decodeEcPrivateKeyFromKeyFile(keyFile, "keyPassword");
```

### 4.7. [Decode EC Public key from PKCS12 file](#47-decode-ec-public-key-from-pkcs12-file)

This API can be used to decode the RSA 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");
```

### 4.8. [Decode EC Public key from the certificate file](#48-decode-ec-public-key-from-the-certificate-file)

This API can be used to decode the EC public key from the given public certificate file.

```ballerina
string certFile = "/path/to/public.cert";
crypto:PublicKey publicKey = check crypto:decodeEcPublicKeyFromCertFile(certFile);
```

### 4.9. [Build RSA Public key from modulus and exponent parameters](#49-build-rsa-public-key-from-modulus-and-exponent-parameters)

This API can be used to build the RSA public key from the given modulus and exponent parameters.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,22 @@ private static Object getPrivateKey(BMap<BString, BString> keyStoreRecord, BStri
}

public static Object decodeRsaPrivateKeyFromKeyFile(BString keyFilePath, Object keyPassword) {
Object decodedPrivateKey = getPrivateKey(keyFilePath, keyPassword);
if (decodedPrivateKey instanceof PrivateKey privateKey) {
return buildRsPrivateKeyRecord(privateKey);
}
return decodedPrivateKey;
}

public static Object decodeEcPrivateKeyFromKeyFile(BString keyFilePath, Object keyPassword) {
Object decodedPrivateKey = getPrivateKey(keyFilePath, keyPassword);
if (decodedPrivateKey instanceof PrivateKey privateKey) {
return buildEcPrivateKeyRecord(privateKey);
}
return decodedPrivateKey;
}

private static Object getPrivateKey(BString keyFilePath, Object keyPassword) {
Security.addProvider(new BouncyCastleProvider());
File privateKeyFile = new File(keyFilePath.getValue());
try (PEMParser pemParser = new PEMParser(new FileReader(privateKeyFile, StandardCharsets.UTF_8))) {
Expand Down Expand Up @@ -151,7 +167,7 @@ public static Object decodeRsaPrivateKeyFromKeyFile(BString keyFilePath, Object
keyFilePath.getValue());
}
PrivateKey privateKey = converter.getPrivateKey(privateKeyInfo);
return buildRsPrivateKeyRecord(privateKey);
return privateKey;
} catch (FileNotFoundException e) {
return CryptoUtils.createError("Key file not found at: " + privateKeyFile.getAbsoluteFile());
} catch (PKCSException | IOException e) {
Expand All @@ -177,7 +193,7 @@ private static Object getPrivateKeyRecord(PrivateKey privateKey) {
}

private static Object buildEcPrivateKeyRecord(PrivateKey privateKey) {
if (privateKey.getAlgorithm().equals(Constants.EC_ALGORITHM)) {
if (privateKey.getAlgorithm().startsWith(Constants.EC_ALGORITHM)) {
return getPrivateKeyRecord(privateKey);
}
return CryptoUtils.createError("Not a valid EC key");
Expand Down Expand Up @@ -235,6 +251,19 @@ public static Object decodeRsaPublicKeyFromCertFile(BString certFilePath) {
}
}

public static Object decodeEcPublicKeyFromCertFile(BString certFilePath) {
File certFile = new File(certFilePath.getValue());
try (FileInputStream fileInputStream = new FileInputStream(certFile)) {
CertificateFactory certificateFactory = CertificateFactory.getInstance(Constants.CERTIFICATE_TYPE_X509);
X509Certificate certificate = (X509Certificate) certificateFactory.generateCertificate(fileInputStream);
return buildEcPublicKeyRecord(certificate);
} catch (FileNotFoundException e) {
return CryptoUtils.createError("Certificate file not found at: " + certFile.getAbsolutePath());
} catch (CertificateException | IOException e) {
return CryptoUtils.createError("Unable to do public key operations: " + e.getMessage());
}
}

private static Object buildRsaPublicKeyRecord(Certificate certificate) {
BMap<BString, Object> certificateBMap = enrichPublicKeyInfo(certificate);
PublicKey publicKey = certificate.getPublicKey();
Expand Down

0 comments on commit dd15ed6

Please sign in to comment.