1 module chloride.lockbox; 2 3 import chloride.core; 4 import chloride.random : randomArray; 5 6 import std.array : uninitializedArray; 7 8 import deimos.sodium.crypto_box; 9 10 /// 11 unittest { 12 import std..string : representation; 13 immutable ubyte[] message = representation("hello"); 14 auto alice = makeKeyPair(); 15 auto bob = makeKeyPair(); 16 auto box = lockBox(message, bob.publicKey, alice.privateKey); 17 assert(openLockBox(box, alice.publicKey, bob.privateKey) == message); 18 } 19 20 /// 21 unittest { 22 import std..string : representation; 23 immutable ubyte[] message = representation("hello"); 24 auto keys = makeKeyPair(); 25 auto box = sealBox(message, keys.publicKey); 26 assert(openSealedBox(box, keys) == message); 27 } 28 29 alias PublicKey = ubyte[crypto_box_PUBLICKEYBYTES]; 30 alias PrivateKey = ubyte[crypto_box_SECRETKEYBYTES]; 31 alias LockBoxSeed = ubyte[crypto_box_SEEDBYTES]; 32 alias Nonce = ubyte[crypto_box_NONCEBYTES]; 33 alias LockBoxMacLength = crypto_box_MACBYTES; 34 35 /** 36 * Struct containing a key pair for public key encryption 37 */ 38 struct KeyPair { 39 PublicKey publicKey; 40 PrivateKey privateKey; 41 } 42 43 /** 44 * Generate a new random key pair. 45 */ 46 KeyPair makeKeyPair() { 47 KeyPair pair; 48 auto result = crypto_box_keypair(pair.publicKey.ptr, pair.privateKey.ptr); 49 enforceSodium(result == 0); 50 return pair; 51 } 52 53 /** 54 * Generate a key pair from a seed. 55 */ 56 KeyPair makeKeyPair(in LockBoxSeed seed) { 57 KeyPair pair; 58 auto result = crypto_box_seed_keypair(pair.publicKey.ptr, 59 pair.privateKey.ptr, 60 seed.ptr); 61 enforceSodium(result == 0); 62 return pair; 63 } 64 65 /** 66 * Generate a seed suitable for use with makeKeyPair 67 */ 68 alias makeLockBoxSeed = randomArray!LockBoxSeed; 69 70 /** 71 * Generate a nonce suitable for encrypting a lock box. 72 */ 73 alias makeNonce = randomArray!Nonce; 74 75 /** 76 * Struct containing a message encrypted and signed using public key cryptography. 77 * Includes the ciphertext and the nonce used to encrypt it. 78 */ 79 struct LockBox { 80 /// The encrypted message 81 ubyte[] ciphertext; 82 /// The nonce used to encrypt it (and needed for decryption) 83 Nonce nonce; 84 } 85 86 /** 87 * Encrypt a message using a public key for the recipient (`pk`) and signed with the private key of the 88 * sender `sk`. 89 */ 90 ubyte[] encryptLockBox(in ubyte[] message, in Nonce nonce, in PublicKey pk, in PrivateKey sk) { 91 auto cipher = uninitializedArray!(ubyte[])(message.length + LockBoxMacLength); 92 auto result = crypto_box_easy(cipher.ptr, message.ptr, message.length, 93 nonce.ptr, pk.ptr, sk.ptr); 94 enforceSodium(result == 0); 95 return cipher; 96 } 97 98 /** 99 * Decrypt a message using a private key for the recipient (`sk`) and verify the signature with the public 100 * key of the sender (`pk`). Returns null if decryption or verification failed. 101 */ 102 ubyte[] decryptLockBox(in ubyte[] cipher, in Nonce nonce, in PublicKey pk, in PrivateKey sk) in { 103 assert(cipher.length > LockBoxMacLength); 104 } body { 105 auto message = uninitializedArray!(ubyte[])(cipher.length - LockBoxMacLength); 106 auto result = crypto_box_open_easy(message.ptr, cipher.ptr, cipher.length, 107 nonce.ptr, pk.ptr, sk.ptr); 108 if (result == 0) { 109 return message; 110 } else { 111 return null; 112 } 113 } 114 115 /** 116 * Encrypt and sign `message` using public key cryptography. The message 117 * is encrypted using the public key of the recipient (`pk`) and signed with 118 * the private key of the sender (`sk`). 119 */ 120 LockBox lockBox(in ubyte[] message, in PublicKey pk, in PrivateKey sk) { 121 LockBox result = {nonce: makeNonce()}; 122 result.ciphertext = encryptLockBox(message, result.nonce, pk, sk); 123 return result; 124 } 125 126 /** 127 * Decrypt a LockBox encrypted and signed using public key cryptography. 128 * The message is decrypted using the private key of the recipient (`sk`) and 129 * the signature is verified using the public key of the sender (`pk`). 130 * If decryption or verification fails, null is returned. 131 */ 132 ubyte[] openLockBox(in LockBox box, in PublicKey pk, in PrivateKey sk) { 133 return decryptLockBox(box.ciphertext, box.nonce, pk, sk); 134 } 135 136 /** 137 * Anonymously encrypt a message using the recipients public key. A new key pair is generated for 138 * each invocation, so a nonce isn't needed. 139 */ 140 ubyte[] sealBox(in ubyte[] message, in PublicKey pk) { 141 auto cipher = uninitializedArray!(ubyte[])(message.length + crypto_box_SEALBYTES); 142 auto result = crypto_box_seal(cipher.ptr, message.ptr, message.length, pk.ptr); 143 enforceSodium(result == 0); 144 return cipher; 145 } 146 147 /** 148 * Decrypt a sealed box using the recipient's key pair. 149 * If decryption fails, return null. 150 */ 151 ubyte[] openSealedBox(in ubyte[] cipher, in KeyPair keys) in { 152 assert(cipher.length > crypto_box_SEALBYTES); 153 } body { 154 auto message = uninitializedArray!(ubyte[])(cipher.length - crypto_box_SEALBYTES); 155 auto result = crypto_box_seal_open(message.ptr, cipher.ptr, cipher.length, 156 keys.publicKey.ptr, keys.privateKey.ptr); 157 if (result == 0) { 158 return message; 159 } else { 160 return null; 161 } 162 }