Skip to main content

Secure Wallet

Secure Wallet

The secure wallet mechanism lets you securely store and access secrets in InterSystems IRIS without exposing those secrets to application code. In this context, a "secret" refers to any piece of sensitive information required by an application at runtime, including API keys, encryption keys, usernames, and passwords. Secrets are grouped together in collections for convenient access management, and access to these secrets is audited.

Secrets are stored in the IRISSECURITY database, which should be encrypted.

API Overview

The secure wallet API consists of the secrets (%Wallet.Secret) organized in collections (%Wallet.Collection) and a set of operations on those secrets.

A collection is defined by its name and the resources that protect its modification (EditResource) and the use of its secrets (UseResource), and the permissions on those resources (WRITE for the EditResource and READ on the UseResource if no permission is specified).

To create a collection, you must have the %Admin_Wallet:USE privilege and use %Wallet.Collection.Create(), specifying its UseResource and EditResource.

In this example, the MyCollection doesn't specify a permission for either resource, so users need MyUseResource:READ and MyEditResource:WRITE, while MyCollection2 specifies the WRITE permission for both resources:

DO ##class(Security.Resources).Create("MyUseResource")
DO ##class(Security.Resources).Create("MyEditResource")

DO ##class(%Wallet.Collection).Create("MyCollection", {"UseResource" : "MyUseResource", "EditResource" : "MyEditResource"})
DO ##class(%Wallet.Collection).Create("MyCollection2", {"UseResource" : "MyUseResource:WRITE", "EditResource" : "MyEditResource:WRITE"})

You can also set both the EditResource and UseResource fields to the same resource with Resource field. If you don't specify a permission, then they default to WRITE and READ for the EditResource and UseResource, respectively:

DO ##class(Security.Resources).Create("MyResource")
DO ##class(%Wallet.Collection).Create("MyOtherCollection", {"Resource" : "MyResource"})

A secret is defined by its name (CollectionName.SecretName), the collection it belongs to, and its contents, which are determined by the secret's type. A secret must belong to a collection, and this relationship lasts for the lifetime of the secret.

A secret can be several types, each a subclass of %Wallet.Secret.

To create a secret, use the Create() method for your secret type, specifying the secret's name, the collection it belongs to, and its contents. The following example creates a 4096-bit RSA key pair MySecret in the collection MyCollection:

DO ##class(%Wallet.RSA).Create("MyCollection.MySecret", { "Length": 4096 })

The API defines the following operations on secrets, all of which require the USE permission on the collection's EditResource:

  • Modify(Name, Properties): Set the fields of an existing secret.

  • Delete(Name): Delete a secret.

Where:

  • Name: String, the name of the secret in the format CollectionName.SecretName.

  • Properties: Either a dynamic object or subscripted array with the fields required, which varies between secret types.

Privileges

The permissions required to modify and use a collection and its secrets is specified by the EditResource and UseResource fields of the collection.

The %Admin_Wallet:USE permission lets you perform any operation on all secrets and collections and is the minimum required permission for creating collections.

Secrets

A secret can be one of the following types:

  • KeyValue: A generic key-value pair, the usage of which is defined by the Usage field.

  • RSA: RSA public/private key pair or certificate used with various $System.Encryption RSA functions.

  • SymmetricKey: A symmetric key used with various $System.Encryption APIs.

KeyValue

A %Wallet.KeyValue secret stores key-value pairs, like username-password pairs and API keys.

The KeyValue secret type uses the following properties:

  • Name: The name of the secret in the form "CollectionName.SecretName."

  • RequireTLS: Whether TLS is required when the secret is transferred through an HTTP request.

  • AllowedHosts: A list of hosts that can receive the secret. This only applies if RequireTLS is true.

  • Secret: The secret.

  • Usage: A comma-delimited string or dynamic array specifying how the secret can be used. This can be any combination of the following:

For example, to create a %Wallet.KeyValue secret for a username-password pair and API key that can be used for HTTP requests and SOAP web clients:

SET sc = ##class(%Wallet.KeyValue).Create(
    "MyCollection.MyKeyValue", {
    "RequireTLS": true,
    "AllowedHosts": ["my_server.example.com"],
    "Secret": {"user" : "my_user", "password" : "my_password", "token" : "my_api_token"},
    "Usage": ["HTTP", "SOAP"]
})

Because MyKeyValue specifies HTTP, you can pass it to %Net.HttpRequest.UseSecret(). For HTTP Basic Authentication, specify the basic scheme, which automatically uses the secret's username and password fields:

SET sc = http.UseSecret("MyCollection.MyKeyValue", "basic")

The header scheme lets you send the content of the token field in an HTTP header. The following example uses the X-SEC-TOKEN header:

SET sc = http.UseSecret("MyCollection.MyKeyValue", "header", "X-SEC-TOKEN", "token")

Similarly, to use MyKeyValue with a SOAP client, you can pass the secret to %SOAP.WebClient.UseSecret()Opens in a new tab. This example uses the soap schema, which constructs the UsernameToken with the user and password fields:

SET sc = client.UseSecret("MyCollection.MyKeyValue", "soap")

RSA

A %Wallet:RSA secret stores an RSA key pair and can be used to sign, verify, encrypt, and decrypt data. This secret type consists of some combination the following depending on your use case, but minimally, an RSA secret must contain either a public key or certificate.

Secrets containing only a public key or certificate can only be used to verify the signature of a private key on data, whereas secrets containing a private key can also be used to sign, decrypt, and encrypt messages.

The RSA secret type uses the following properties:

  • Certificate and CertificateFile: A PEM-encoded certificate.

  • PublicKey and PublicKeyFile: A PEM-encoded public key.

  • PrivateKey and PrivateKeyFile: A PEM-encoded private key. RSA secrets must have a private key to sign, decrypt, and encrypt messages.

  • Password: A password for decrypting password-protected private keys.

  • Length: The length of the RSA key pair in bytes. You can use this to generate a new key pair.

To generate a new public/private key pair, specify a key Length:

SET sc = ##class(%Wallet.RSA).Create("MyCollection.MyKeyPair", { "Length": 4096 })

To import an existing certificate, public key, or private key, you can either pass in its contents or the path to a file:

// Import the public key directly
SET sc = ##class(%Wallet.RSA).Create("MyCollection.MyImportedPublicKey", { 
    "PublicKey": "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----"
})

// Import the public key from a .pem file
SET sc = ##class(%Wallet.RSA).Create("MyCollection.MyImportedPublicKeyFile", {
    "PublicKeyFile": "path/to/public/key/file.pem"
})

// Import a certificate
SET sc = ##class(%Wallet.RSA).Create("MyCollection.MyImportedPublicKeyFile", {
    "Certificate": "-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----"
})

// Import a certificate from a .pem file
SET sc = ##class(%Wallet.RSA).Create("MyCollection.MyImportedPublicKeyFile", {
    "CertificateFile": "path/to/certificate/file.pem"
})

// Import a private key directly
SET sc = ##class(%Wallet.RSA).Create("MyCollection.MyImportedPrivateKey", {
    "PublicKey": "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----",
    "PrivateKey": "-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----"
})

// Import a private key from a .pem file
SET sc = ##class(%Wallet.RSA).Create("MyCollection.MyImportedPrivateKeyFile", {
    "PublicKey": "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----",
    "PrivateKeyFile": "path/to/private/key/file.pem"
})

// Import an password-protected private key from a .pem file
SET sc = ##class(%Wallet.RSA).Create("MyCollection.MyImportedPrivateKeyFile", {
    "PublicKeyFile": "path/to/public/key/file.pem",
    "PrivateKeyFile": "path/to/encrypted/private/key/file.pem",
    "Password": "mypassword"
})

You can use RSA secrets to sign, verify, encrypt, and decrypt data. These %Wallet.RSA methods act as wrappers for their counterparts in $SYSTEM.Encryption:

  • SHASign()

  • SHAVerify()

  • SHA3Sign()

  • SHA3Verify()

The following examples use these methods to sign and verify data:

  • Sign and verify a string (MyCollection.MySecret contains a public and private key, while MyOtherCollection.MyOtherSecret contains only a public key):

    // RSA-SHA with 256 bits, equivalent to calling $SYSTEM.Encryption.RSASHASign() and $SYSTEM.Encryption.RSASHAVerify()
    SET sigsha = ##class(%Wallet.RSA).SHASign("MyCollection.MySecret", 256, mymessage)
    SET validsha = ##class(%Wallet.RSA).SHAVerify("MyOtherCollection.MyOtherSecret", 256, mymessage, sigsha)
    
    // RSA-SHA3 with 256 bits, equivalent to calling $SYSTEM.Encryption.RSASHA3Sign() and $SYSTEM.Encryption.RSASHA3Verify()
    SET sigsha3 = ##class(%Wallet.RSA).SHA3Sign("MyCollection.MySecret", 256, mymessage)
    SET validsha3 = ##class(%Wallet.RSA).SHA3Verify("MyOtherCollection.MyOtherSecret", 256, mymessage, sigsha3)
    
  • Encrypt and decrypt a string:

    // equivalent to calling $SYSTEM.Encryption.RSAEncrypt() and $SYSTEM.Encryption.RSADecrypt()
    SET cipher = ##class(%Wallet.RSA).Encrypt("MyCollection.MySecret", mymessage)
    SET plaintext = ##class(%Wallet.RSA).Decrypt("MyOtherCollection.MyOtherSecret", cipher)
    

Symmetric Key

A %Wallet.SymmetricKey secret stores a symmetric key used for various encryption, decryption, and hashing algorithms. You can create a new %Wallet.SymmetricKey by specifying a length or secret.

  • To generate a new key with a certain length, specify its Length (in bytes):

    SET sc = ##class(%Wallet.SymmetricKey).Create("MyCollection.MySecret", { "Length":32 })
    
  • To generate a key from a secret, specify a Secret with the desired byte length. This example generates and uses a random 16-byte secret:

    SET key = $System.Encryption.GenCryptRand(16,1)
    SET sc = ##class(%Wallet.SymmetricKey).Create("MyCollection.MySecret", { "Secret":(key) })
    
  • To generate a key from a Base64-encoded secret, use the Secret64 property:

    SET key = $System.Encryption.GenCryptRand(16,1)
    SET encodedKey = $System.Encryption.Base64Encode(key)
    SET sc = ##class(%Wallet.SymmetricKey).Create("MyCollection.MySecret", { "Secret64":(encodedKey) })
    
  • To use a secret from a file:

    // Create a new key file and get its identifier
    SET identifier = $System.Encryption.CreateEncryptionKey("path/to/key/file", "username", "password", 16, sc)
    // Import the key file as a secret
    SET sc = ##class(%Wallet.SymmetricKey).CreateFromKeyFile("path/to/key/file", "username", "password", "identifier", "MyCollection.MySecretFromFile")
    

You can then use %Wallet.SymmetricKey to encrypt, decrypt, and hash your data. These %Wallet.SymmetricKey methods call their counterparts in %SYSTEM.EncryptionOpens in a new tabOpens in a new tab:

  • AESCBC encryption (AESCBCEncrypt() and AESCBCDecrypt())

    set message = "Hello world"
    set iv = $System.Encryption.GenCryptRand(12,1)
    
    // encrypt the message
    set ciphertext = ##class(%Wallet.SymmetricKey).AESCBCEncrypt("MyCollection.MySymmetricKey", message, iv)
    
    // decrypt the message
    set plaintext = ##class(%Wallet.SymmetricKey).AESCBCDecrypt("MyCollection.MySymmetricKey", ciphertext, iv)
    
  • AESCBC Stream encryption (AESCBCEncryptStream() and AESCBCDecryptStream())

    SET message = ##class(%Stream.GlobalCharacter).%New()
    SET ciphertext = ##class(%Stream.GlobalBinary).%New()
    SET plaintext = ##class(%Stream.GlobalCharacter).%New()	
    DO message.Write("Hello world")
    SET iv = $System.Encryption.GenCryptRand(12,1)
    
    // encrypt the message
    DO ##class(%Wallet.SymmetricKey).AESCBCEncryptStream("MyCollection.MySymmetricKey", message, ciphertext, iv)
    
    // decrypt the ciphertext
    DO ##class(%Wallet.SymmetricKey).AESCBCDecryptStream("MyCollection.MySymmetricKey", ciphertext, plaintext, iv)
    
  • AESGCM encryption (AESGCMEncrypt() and AESGCMDecrypt())

    SET message = "Hello world"
    SET inParams = {
        "iv":($System.Encryption.GenCryptRand(12,1)),
        "aad":($System.Encryption.GenCryptRand(12,1))
    }
    
    // encrypt with default input parameters
    SET ciphertext = ##class(%Wallet.SymmetricKey).AESGCMEncrypt("MyCollection.MySymmetricKey", message,, .outParams)
    
    // encrypt with optional input parameters
    SET ciphertext = ##class(%Wallet.SymmetricKey).AESGCMEncrypt("MyCollection.MySymmetricKey", message, inParams, .outParams)
    
    // decrypt the ciphertext using the output parameters from AESGCMEEncrypt()
    SET plaintext = ##class(%Wallet.SymmetricKey).AESGCMDecrypt("MyCollection.MySymmetricKey", ciphertext, outParams)
    
  • AES Key Wrap (AESKeyWrap() and AESKeyUnwrap())

    // create key encryption key
    set enckey = $System.Encryption.GenCryptRand(128)
    
    // encrypt the encryption key with the symmetric key, equivalent to calling $SYSTEM.Encryption.AESKeyWrap()
    set enc = ##class(%Wallet.SymmetricKey).AESKeyWrap("MyCollection.MySymmetricTestKey", enckey)
    
    // decrypt the encryption key with the symmetric key, equivalent to calling $SYSTEM.Encryption.AESKeyUnwrap()
    set denc = ##class(%Wallet.SymmetricKey).AESKeyUnwrap("MyCollection.MySymmetricTestKey", enc)
    
  • HMACSHA (HMACSHA() and HMACSHA3())

    // SHA-1, equivalent to calling $SYSTEM.Encryption.HMACSHA()
    // SHA-1 is no longer considered secure and, in general, you should use SHA-3 (by using HMACSHA3) instead
    SET hmac = ##class(%Wallet.SymmetricKey).HMACSHA("MyCollection.MySymmetricKey", 256, "hello world")
    
    // SHA-3, equivalent to calling $SYSTEM.Encryption.HMACSHA3()
    SET hmac3 = ##class(%Wallet.SymmetricKey).HMACSHA3("MyCollection.MySymmetricKey", 256, "hello world")
    
    
  • HMACSHA for Streams (HMACSHAStream() and HMACSHA3Stream())

    SET message = ##class(%Stream.GlobalCharacter).%New()
    message.Write("Hello world")
    
    // SHA-1, equivalent to calling $SYSTEM.Encryption.HMACSHAStream()
    // SHA-1 is no longer considered secure and, in general, you should use SHA-3 (by using HMACSHA3Stream) instead
    SET hmac = $System.Encryption.HMACSHAStream(256, message, "MyCollection.MySymmetricTestKey", .sc)
    
    // SHA-3, equivalent to calling $SYSTEM.Encryption.HMACSHA3Stream()
    SET hmac3 = $System.Encryption.HMACSHA3Stream(256, message, "MyCollection.MySymmetricTestKey", .sc)
    

Auditing

Secrets are audited with the following events. For details on these and other events, see Auditing:

  • %System/%Security/WalletSecretChange: Triggered when a user creates, modifies, or deletes a secret.

  • %System/%Security/WalletSecretUse: Triggered when a user attempts to use a secret.

  • %System/%Security/AccessDenied: Triggered when a user attempts to use a secret without the correct permission on the collection's UseResource.

Importing and Exporting Secrets

Secrets can be imported and exported as a collection with %Wallet.Collection:Import() %Wallet.Collection:Export(). Both methods require %Admin_Wallet:U.

The following examples use numCollections and numSecrets as output parameters for the number of secrets and collections imported or exported.

You can export a subset of your collections and secrets with a comma-delimited list of names; if unspecified, these patterns are set to * (asterisk) by default to export all collections and secrets:

// Export all collections and all secrets to "/exportedSecrets.txt"
SET sc = ##class(%Wallet.Collection).Export("/exportedSecrets.txt", .numCollections, .numSecrets)
SET sc = ##class(%Wallet.Collection).Export("/exportedSecrets.txt", .numCollections, .numSecrets, *, *)

// Export all secrets from "MyCollection"
SET sc = ##class(%Wallet.Collection).Export("/exportedSecrets.txt", .numCollections, .numSecrets, "MyCollection")

// Export all secrets with the name "MySecret" and "MyOtherSecret" from all collections
SET sc = ##class(%Wallet.Collection).Export("/exportedSecrets.txt", .numCollections, .numSecrets, *, "MySecret,MyOtherSecret")
    
// Export secrets with the name "MySecret" from both "MyCollection" and "MyOtherCollection"
SET sc = ##class(%Wallet.Collection).Export("/exportedSecrets.txt", .numCollections, .numSecrets, "MyCollection,MyOtherCollection", "MySecret")
    
// Export all collections, but no secrets
SET sc = ##class(%Wallet.Collection).Export("/exportedSecrets.txt", .numCollections, .numSecrets, *, "")

You can then import all secrets from a specified file. The collections and secrets in the imported file must not exist in your database:

SET sc = ##class(%Wallet.Collection).Import("/iris-shared/exportedSecrets.txt", .numCollections, .numSecrets)

FeedbackOpens in a new tab