This guide outlines a simplified method for creating and uploading an Oracle Wallet to an Oracle RDS instance. Unlike the AWS approach, which involves using an object store and UTL_HTTP, this method uses SQL Developer to upload the wallet as a BLOB to a table and write it directly to the Oracle directory.
Step 1:
I could not have solved this issue without the help of Ilmar Kerm. See the link he shared with me here. The script below pulls each cert from the tls-ca-bundle that is provided on most Linux systems and adds them to a new wallet. This wallet can then be uploaded to RDS.
#!/usr/bin/env bash
# Input file containing multiple certificates
input_file="${1:-/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem}"
cert_num=1
# The first time the code below writes cert_file is not defined and would throw an error.
cert_file=/dev/null
mv ./cwallet.sso ./cwallet.sso.$(date +%Y%m%d%H%M%S) 2>/dev/null
# Create a new cwallet.sso file.
orapki wallet create -wallet . -auto_login_only
# Read the input file line by line
while IFS= read -r line; do
if [[ "$line" =~ ^-----BEGIN[[:space:]]+CERTIFICATE----- ]]; then
# Start a new certificate file
cert_file="./cert_${cert_num}.pem"
echo "$line" > "$cert_file"
cert_num=$((cert_num + 1))
elif [[ "$line" =~ ^-----END[[:space:]]+CERTIFICATE----- ]]; then
# End the current certificate file
echo "$line" >> "$cert_file"
# Add the cert to the wallet.
orapki wallet add -wallet . -trusted_cert -cert $cert_file -auto_login_only
rm $cert_file
else
# Add the line to the current certificate file
echo "$line" >> "$cert_file"
fi
done < "$input_file"
# List the certs in the wallet.
orapki wallet display -wallet .
exit 0
Step 2: Retrieve the SSL Certificates
This step is no longer necessary but I am leaving it here for reference. You can also review the certificate authority and make sure the tls-ca-bundle.pem file contains a cert for that authority.
echo | openssl s_client -connect smtp.office365.com:587 -starttls smtp -showcerts
Also leaving this here for reference if you need to remove a cert from a wallet.
orapki wallet remove -wallet . -trusted_cert -alias "CN=DigiCert Global Root CA" -auto_login_only
Step 3: Copy the Wallet to a Server with SQL Developer Access
Once the wallet is configured, copy the cwallet.sso
file to a server that has SQL Developer installed and can access the RDS instance. This ensures that you can use SQL Developer to interact with the RDS database while utilizing the wallet for secure connections.
Step 4: Create a Table to Store the Wallet File
Create a table in SQL Developer to upload the wallet file into a directory:
CREATE TABLE my_files (
id NUMBER PRIMARY KEY,
file_name VARCHAR2(255),
file_data BLOB);
Use native SQL Developer interface to add a row (id=1) with the file name of cwallet.sso
and the file data being the contents of the file. SQL Developer will allow you to upload the file as a blob by selecting it.
Step 5: Create a Directory for the Wallet
Next, create a directory on the RDS instance to store the wallet file:
exec rdsadmin.rdsadmin_util.create_directory('WALLET');
Step 6: Write the Wallet File to the directory
Use the following PL/SQL code to write the wallet file to the directory:
DECLARE
v_file UTL_FILE.FILE_TYPE;
v_buffer RAW(32767);
v_amount BINARY_INTEGER := 32767;
v_pos INTEGER := 1;
v_blob BLOB;
v_blob_len INTEGER;
v_filename VARCHAR2(255);
BEGIN
-- Retrieve the BLOB and filename from the table
SELECT file_data, file_name
INTO v_blob, v_filename
FROM my_files
WHERE id = 1; -- Adjust this as necessary to select the correct row
v_blob_len := DBMS_LOB.getlength(v_blob);
-- Open the file for writing in binary mode, this will zero out the file
v_file := UTL_FILE.fopen('WALLET', v_filename, 'wb', 32767);
-- Close the file immediately to ensure it's zeroed out
UTL_FILE.fclose(v_file);
-- Reopen the file for appending
v_file := UTL_FILE.fopen('WALLET', v_filename, 'ab', 32767);
-- Write the BLOB to the file in chunks
WHILE v_pos <= v_blob_len LOOP
DBMS_LOB.read(v_blob, v_amount, v_pos, v_buffer);
UTL_FILE.put_raw(v_file, v_buffer, TRUE);
v_pos := v_pos + v_amount;
END LOOP;
-- Close the file
UTL_FILE.fclose(v_file);
EXCEPTION
WHEN OTHERS THEN
IF UTL_FILE.is_open(v_file) THEN
UTL_FILE.fclose(v_file);
END IF;
RAISE;
END;
/
Step 7: Configure and Test the SMTP Connection
After opening outbound access to the SMTP server, use the following PL/SQL code to test sending an email:
DECLARE
l_smtp_server VARCHAR2(1024) := 'smtp.office365.com';
l_smtp_port NUMBER := 25;
l_wallet_dir VARCHAR2(128) := 'WALLET';
l_from VARCHAR2(128) := 'NoReply@somecompany.com';
l_to VARCHAR2(128) := 'ethan.post@somecompany.com';
l_user VARCHAR2(128) := 'NoReply@somecompany.com';
l_password VARCHAR2(128) := 'BobTheBuilder123NotARealPassword';
l_subject VARCHAR2(128) := 'Test subject';
l_wallet_path VARCHAR2(4000);
l_conn UTL_SMTP.CONNECTION;
l_reply UTL_SMTP.REPLY;
l_replies UTL_SMTP.REPLIES;
BEGIN
SELECT 'file:/' || directory_path INTO l_wallet_path
FROM dba_directories
WHERE directory_name = l_wallet_dir;
-- Open a connection
l_reply := UTL_SMTP.OPEN_CONNECTION(
host => l_smtp_server,
port => l_smtp_port,
c => l_conn,
wallet_path => l_wallet_path,
secure_connection_before_smtp => FALSE
);
DBMS_OUTPUT.PUT_LINE('Opened connection, received reply ' || l_reply.code || '/' || l_reply.text);
-- Get supported configs from the server
l_replies := UTL_SMTP.EHLO(l_conn, 'smtp.office365.com');
FOR r IN 1..l_replies.COUNT LOOP
DBMS_OUTPUT.PUT_LINE('EHLO (server config): ' || l_replies(r).code || '/' || l_replies(r).text);
END LOOP;
-- STARTTLS
l_reply := UTL_SMTP.STARTTLS(l_conn);
DBMS_OUTPUT.PUT_LINE('STARTTLS, received reply ' || l_reply.code || '/' || l_reply.text);
-- EHLO again after STARTTLS
l_replies := UTL_SMTP.EHLO(l_conn, 'smtp.office365.com');
FOR r IN 1..l_replies.COUNT LOOP
DBMS_OUTPUT.PUT_LINE('EHLO (server config): ' || l_replies(r).code || '/' || l_replies(r).text);
END LOOP;
-- Authenticate and send the email
UTL_SMTP.AUTH(l_conn, l_user, l_password, UTL_SMTP.ALL_SCHEMES);
UTL_SMTP.MAIL(l_conn, l_from);
UTL_SMTP.RCPT(l_conn, l_to);
UTL_SMTP.OPEN_DATA(l_conn);
UTL_SMTP.WRITE_DATA(l_conn, 'Date: ' || TO_CHAR(SYSDATE, 'DD-MON-YYYY HH24:MI:SS') || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(l_conn, 'From: ' || l_from || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(l_conn, 'To: ' || l_to || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(l_conn, 'Subject: ' || l_subject || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(l_conn, '' || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(l_conn, 'Test message.' || UTL_TCP.CRLF);
UTL_SMTP.CLOSE_DATA(l_conn);
-- Quit the connection
l_reply := UTL_SMTP.QUIT(l_conn);
EXCEPTION
WHEN OTHERS THEN
UTL_SMTP.QUIT(l_conn);
RAISE;
END;
/