diff --git a/README.md b/README.md index 809d9f3..4bd0022 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,19 @@ nonce, encrypted = Cox.encrypt(data, bob.public, alice.secret) decrypted = Cox.decrypt(encrypted, nonce, alice.public, bob.secret) String.new(decrypted) # => "Hello World!" + + +# Public key signing + +message = "Hello World!" + +signing_pair = Cox::SignKeyPair.new + +# Sign the message +signature = Cox.sign_detached(message, signing_pair.secret) + +# And verify +Cox.verify_detached(signature, message, signing_pair.public) # => true ``` ## Contributing @@ -53,3 +66,4 @@ String.new(decrypted) # => "Hello World!" ## Contributors - [andrewhamon](https://github.com/andrewhamon) Andrew Hamon - creator, maintainer +- [dorkrawk](https://github.com/dorkrawk) Dave Schwantes - contributor diff --git a/spec/cox_spec.cr b/spec/cox_spec.cr index 1195ac9..d0afed6 100644 --- a/spec/cox_spec.cr +++ b/spec/cox_spec.cr @@ -3,7 +3,7 @@ require "./spec_helper" describe Cox do # TODO: Write tests - it "works" do + it "works for encrypting" do data = "Hello World!" # Alice is the sender @@ -22,4 +22,18 @@ describe Cox do String.new(decrypted).should eq(data) end + + it "works for signing" do + message = "test" + + signing_pair = Cox::SignKeyPair.new + + # Create signature using the secret key + signature = Cox.sign_detached(message, signing_pair.secret) + + # Verify the signature on the message + verified = Cox.verify_detached(signature, message, signing_pair.public) + + verified.should eq(true) + end end diff --git a/src/cox.cr b/src/cox.cr index 8ed4a9c..96169fe 100644 --- a/src/cox.cr +++ b/src/cox.cr @@ -21,6 +21,24 @@ module Cox LibSodium.crypto_box_open_easy(output_buffer.to_unsafe, data_buffer.to_unsafe, data_size, nonce.pointer, sender_public_key.pointer, recipient_secret_key.pointer) output_buffer end + + def self.sign_detached(message, secret_key : SignSecretKey) + message_buffer = message.to_slice + message_buffer_size = message_buffer.bytesize + signature_output_buffer = Bytes.new(LibSodium::SIGNATURE_BYTES) + + LibSodium.crypto_sign_detached(signature_output_buffer.to_unsafe, 0, message_buffer.to_unsafe, message_buffer_size, secret_key.pointer) + signature_output_buffer + end + + def self.verify_detached(signature, message, public_key : SignPublicKey) + signature_buffer = signature.to_slice + message_buffer = message.to_slice + message_buffer_size = message_buffer.bytesize + + verified = LibSodium.crypto_sign_verify_detached(signature_buffer.to_unsafe, message_buffer.to_unsafe, message_buffer_size, public_key.pointer) + verified.zero? + end end if Cox::LibSodium.sodium_init() == -1 diff --git a/src/cox/lib_sodium.cr b/src/cox/lib_sodium.cr index 7522dbc..8139fb0 100644 --- a/src/cox/lib_sodium.cr +++ b/src/cox/lib_sodium.cr @@ -3,15 +3,21 @@ module Cox lib LibSodium fun sodium_init() : LibC::Int - fun crypto_box_publickeybytes() : LibC::SizeT - fun crypto_box_secretkeybytes() : LibC::SizeT - fun crypto_box_noncebytes() : LibC::SizeT - fun crypto_box_macbytes() : LibC::SizeT + fun crypto_box_publickeybytes() : LibC::SizeT + fun crypto_box_secretkeybytes() : LibC::SizeT + fun crypto_box_noncebytes() : LibC::SizeT + fun crypto_box_macbytes() : LibC::SizeT + fun crypto_sign_publickeybytes() : LibC::SizeT + fun crypto_sign_secretkeybytes() : LibC::SizeT + fun crypto_sign_bytes() : LibC::SizeT - PUBLIC_KEY_BYTES = crypto_box_publickeybytes() - SECRET_KEY_BYTES = crypto_box_secretkeybytes() - NONCE_BYTES = crypto_box_macbytes() - MAC_BYTES = crypto_box_macbytes() + PUBLIC_KEY_BYTES = crypto_box_publickeybytes() + SECRET_KEY_BYTES = crypto_box_secretkeybytes() + NONCE_BYTES = crypto_box_macbytes() + MAC_BYTES = crypto_box_macbytes() + PUBLIC_SIGN_BYTES = crypto_sign_publickeybytes() + SECRET_SIGN_BYTES = crypto_sign_secretkeybytes() + SIGNATURE_BYTES = crypto_sign_bytes() fun crypto_box_keypair( public_key_output : Pointer(LibC::UChar), @@ -35,5 +41,25 @@ module Cox sender_public_key : Pointer(LibC::UChar), recipient_secret_key : Pointer(LibC::UChar) ) : LibC::Int + + fun crypto_sign_keypair( + public_key_output : Pointer(LibC::UChar), + secret_key_output : Pointer(LibC::UChar) + ) : LibC::Int + + fun crypto_sign_detached( + signature_output : Pointer(LibC::UChar), + signature_output_size : LibC::ULongLong, + message : Pointer(LibC::UChar), + message_size : LibC::ULongLong, + secret_key : Pointer(LibC::UChar) + ) : LibC::Int + + fun crypto_sign_verify_detached( + signature : Pointer(LibC::UChar), + message : Pointer(LibC::UChar), + message_size : LibC::ULongLong, + public_key : Pointer(LibC::UChar) + ) : LibC::Int end end diff --git a/src/cox/sign_key_pair.cr b/src/cox/sign_key_pair.cr new file mode 100644 index 0000000..0b64882 --- /dev/null +++ b/src/cox/sign_key_pair.cr @@ -0,0 +1,24 @@ +require "./lib_sodium" + +module Cox + class SignKeyPair + property public : SignPublicKey + property secret : SignSecretKey + + def initialize(@public, @secret) + end + + def self.new(pub : Bytes, sec : Bytes) + new(SignPublicKey.new(pub), SignSecretKey.new(sec)) + end + + def self.new + public_key = Bytes.new(SignPublicKey::KEY_LENGTH) + secret_key = Bytes.new(SignSecretKey::KEY_LENGTH) + + LibSodium.crypto_sign_keypair(public_key.to_unsafe, secret_key.to_unsafe) + + new(public_key, secret_key) + end + end +end diff --git a/src/cox/sign_public_key.cr b/src/cox/sign_public_key.cr new file mode 100644 index 0000000..030c3c7 --- /dev/null +++ b/src/cox/sign_public_key.cr @@ -0,0 +1,15 @@ +require "./lib_sodium" + +module Cox + class SignPublicKey < Key + property bytes : Bytes + + KEY_LENGTH = LibSodium::PUBLIC_SIGN_BYTES + + def initialize(@bytes : Bytes) + if bytes.bytesize != KEY_LENGTH + raise ArgumentError.new("Public key must be #{KEY_LENGTH} bytes, got #{bytes.bytesize}") + end + end + end +end diff --git a/src/cox/sign_secret_key.cr b/src/cox/sign_secret_key.cr new file mode 100644 index 0000000..332099b --- /dev/null +++ b/src/cox/sign_secret_key.cr @@ -0,0 +1,15 @@ +require "./lib_sodium" + +module Cox + class SignSecretKey < Key + property bytes : Bytes + + KEY_LENGTH = LibSodium::SECRET_SIGN_BYTES + + def initialize(@bytes : Bytes) + if bytes.bytesize != KEY_LENGTH + raise ArgumentError.new("Secret key must be #{KEY_LENGTH} bytes, got #{bytes.bytesize}") + end + end + end +end