diff --git a/ballerina/private_public_key.bal b/ballerina/private_public_key.bal index a667388f..d37ffe59 100644 --- a/ballerina/private_public_key.bal +++ b/ballerina/private_public_key.bal @@ -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 = { @@ -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..."; diff --git a/ballerina/tests/private_public_key_test.bal b/ballerina/tests/private_public_key_test.bal index d8b86539..8ac867fd 100644 --- a/ballerina/tests/private_public_key_test.bal +++ b/ballerina/tests/private_public_key_test.bal @@ -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); @@ -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 = publicKey["certificate"]; + + string serial = (certificate["serial"]).toString(); + string issuer = certificate["issuer"]; + string subject = certificate["subject"]; + string signingAlgorithm = 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); diff --git a/ballerina/tests/resources/ec-cert.crt b/ballerina/tests/resources/ec-cert.crt new file mode 100644 index 00000000..25f1d4ab --- /dev/null +++ b/ballerina/tests/resources/ec-cert.crt @@ -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----- \ No newline at end of file diff --git a/ballerina/tests/test_utils.bal b/ballerina/tests/test_utils.bal index 0f2cc0e8..30015648 100644 --- a/ballerina/tests/test_utils.bal +++ b/ballerina/tests/test_utils.bal @@ -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"; diff --git a/changelog.md b/changelog.md index 3182dadb..bb516ad6 100644 --- a/changelog.md +++ b/changelog.md @@ -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) diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 42ed0851..6c3f829f 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -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) @@ -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 = { @@ -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. diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decode.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decode.java index 059a2905..a4557b32 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decode.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Decode.java @@ -119,6 +119,22 @@ private static Object getPrivateKey(BMap 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))) { @@ -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) { @@ -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"); @@ -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 certificateBMap = enrichPublicKeyInfo(certificate); PublicKey publicKey = certificate.getPublicKey();