Breaking API changes:

SignSecretKey rename to Sign::SecretKey
  SignPublicKey rename to Sign::PublicKey
  SignKeyPair removed.  Use Sign::SecretKey instead.

  Cox.sign_detached moved to Sign::SecretKey#sign_detached
  Cox.verify_detached moved to Sign::PublicKey#verify_detached

  verify_detached raises on failure instead of using a return value.

  More validation of data sizes.
master
Didactic Drunk 2019-06-28 03:30:33 -07:00
parent da8f97ae47
commit 0eb4a8991a
12 changed files with 114 additions and 103 deletions

View File

@ -9,7 +9,7 @@ Updated Crystal bindings for the [libsodium API](https://libsodium.gitbook.io/do
- Public-Key Cryptography
- [x] Crypto Box Easy
- [ ] Sealed Box
- [x] Combined Signatures
- [ ] Combined Signatures
- [x] Detached Signatures
- [Secret-Key Cryptography](https://libsodium.gitbook.io/doc/secret-key_cryptography)
- Secret Box
@ -89,13 +89,17 @@ String.new(decrypted) # => "Hello World!"
```crystal
message = "Hello World!"
signing_pair = Cox::SignKeyPair.new
secret_key = Cox::Sign::SecretKey.new
# Sign the message
signature = Cox.sign_detached(message, signing_pair.secret)
signature = secret_key.sign_detached message
# And verify
Cox.verify_detached(signature, message, signing_pair.public) # => true
# Send secret_key.public_key to the recipient
public_key = Cox::Sign::PublicKey.new key_bytes
# raises Cox::Error::VerificationFailed on failure.
public_key.verify_detached message, signature
```
### Secret Key Encryption

View File

@ -9,7 +9,7 @@ describe Cox::SecretKey do
decrypted = key.decrypt_easy encrypted, nonce
message.should eq String.new(decrypted)
expect_raises(Cox::DecryptionFailed) do
expect_raises(Cox::Error::DecryptionFailed) do
key.decrypt_easy "badmsgbadmsgbadmsgbadmsgbadmsg".to_slice, nonce
end
end

View File

@ -0,0 +1,22 @@
require "../../spec_helper"
require "../../../src/cox/sign/secret_key"
describe Cox::Sign::SecretKey do
it "signs and verifies" do
message = "foo"
skey = Cox::Sign::SecretKey.new
sig = skey.sign_detached message
skey.public_key.verify_detached message, sig
end
it "signs and fails" do
message = "foo"
skey = Cox::Sign::SecretKey.new
sig = skey.sign_detached message
expect_raises Cox::Error::VerificationFailed do
skey.public_key.verify_detached "bar", sig
end
end
end

View File

@ -22,18 +22,4 @@ 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

View File

@ -2,12 +2,11 @@ require "random/secure"
module Cox
class Error < ::Exception
end
class VerificationFailed < Error
end
class VerificationFailed < Error
end
class DecryptionFailed < Error
class DecryptionFailed < Error
end
end
end
@ -34,30 +33,10 @@ module Cox
data_size = data_buffer.bytesize
output_buffer = Bytes.new(data_buffer.bytesize - LibSodium::MAC_SIZE)
if LibSodium.crypto_box_open_easy(output_buffer.to_slice, data_buffer.to_slice, data_size, nonce.to_slice, sender_public_key.to_slice, recipient_secret_key.to_slice) != 0
raise DecryptionFailed.new("crypto_box_open_easy")
raise Error::DecryptionFailed.new("crypto_box_open_easy")
end
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_SIZE)
if LibSodium.crypto_sign_detached(signature_output_buffer.to_slice, 0, message_buffer.to_slice, message_buffer_size, secret_key.to_slice) != 0
raise Error.new("crypto_sign_detached")
end
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_slice, message_buffer.to_slice, message_buffer_size, public_key.to_slice)
verified.zero?
end
end
if Cox::LibSodium.sodium_init == -1

View File

@ -108,7 +108,7 @@ module Cox
fun crypto_sign_detached(
signature_output : Pointer(LibC::UChar),
signature_output_size : LibC::ULongLong,
signature_output_size : Pointer(LibC::ULongLong),
message : Pointer(LibC::UChar),
message_size : LibC::ULongLong,
secret_key : Pointer(LibC::UChar)

View File

@ -48,7 +48,7 @@ module Cox
def decrypt_easy(data : Bytes, nonce : Nonce) : Bytes
output_size = data.bytesize - MAC_SIZE
raise Cox::DecryptionFailed.new("encrypted data too small #{data.bytesize}") if output_size <= 0
raise Cox::Error::DecryptionFailed.new("encrypted data too small #{data.bytesize}") if output_size <= 0
output = Bytes.new output_size
decrypt_easy(data, output, nonce)
end
@ -58,7 +58,7 @@ module Cox
raise ArgumentError.new("dst.bytesize must be src.bytesize - MAC_SIZE, got #{dst.bytesize}")
end
if LibSodium.crypto_secretbox_open_easy(dst, src, src.bytesize, nonce.to_slice, @bytes) != 0
raise Cox::DecryptionFailed.new("crypto_secretbox_easy")
raise Cox::Error::DecryptionFailed.new("crypto_secretbox_easy")
end
dst
end

View File

@ -0,0 +1,28 @@
require "../lib_sodium"
module Cox
class Sign::PublicKey < Key
property bytes : Bytes
KEY_SIZE = LibSodium::PUBLIC_SIGN_SIZE
def initialize(@bytes : Bytes)
if bytes.bytesize != KEY_SIZE
raise ArgumentError.new("Public key must be #{KEY_SIZE} bytes, got #{bytes.bytesize}")
end
end
def verify_detached(message, sig : Bytes)
verify_detached message.to_slice, sig
end
def verify_detached(message : Bytes, sig : Bytes)
raise ArgumentError.new("Signature must be #{LibSodium::SIGNATURE_SIZE} bytes, got #{sig.bytesize}")
v = LibSodium.crypto_sign_verify_detached sig, message, message.bytesize, @bytes
if v != 0
raise Cox::Error::VerificationFailed.new("crypto_sign_verify_detached")
end
end
end
end

View File

@ -0,0 +1,46 @@
require "../lib_sodium"
module Cox
class Sign::SecretKey < Cox::Key
KEY_SIZE = LibSodium::SECRET_SIGN_SIZE
getter bytes : Bytes
getter public_key
# Generates a new secret/public key pair.
def initialize
pkey = Bytes.new(Sign::PublicKey::KEY_SIZE)
@bytes = Bytes.new(KEY_SIZE)
@public_key = PublicKey.new pkey
LibSodium.crypto_sign_keypair pkey, @bytes
end
# Use existing Private and Public keys.
def initialize(@bytes : Bytes, pkey : Bytes)
raise ArgumentError.new("Secret sign key must be #{KEY_SIZE}, got #{@bytes.bytesize}")
@public_key = PublicKey.new pkey
end
# def initialize(@bytes : Bytes)
# if bytes.bytesize != KEY_SIZE
# raise ArgumentError.new("Secret key must be #{KEY_SIZE} bytes, got #{bytes.bytesize}")
# end
# BUG: fix
# @public_key = PublicKey.new Bytes.new(100)
# raise "Needs crypto_sign_ed25519_sk_to_pk"
# Also needs to differentiate from seed as a single parameter
# end
def sign_detached(message)
sign_detached message.to_slice
end
def sign_detached(message : Bytes)
sig = Bytes.new(LibSodium::SIGNATURE_SIZE)
if LibSodium.crypto_sign_detached(sig, out sig_len, message, message.bytesize, @bytes) != 0
raise Error.new("crypto_sign_detached")
end
sig
end
end
end

View File

@ -1,24 +0,0 @@
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_SIZE)
secret_key = Bytes.new(SignSecretKey::KEY_SIZE)
LibSodium.crypto_sign_keypair(public_key.to_unsafe, secret_key.to_unsafe)
new(public_key, secret_key)
end
end
end

View File

@ -1,15 +0,0 @@
require "./lib_sodium"
module Cox
class SignPublicKey < Key
property bytes : Bytes
KEY_SIZE = LibSodium::PUBLIC_SIGN_SIZE
def initialize(@bytes : Bytes)
if bytes.bytesize != KEY_SIZE
raise ArgumentError.new("Public key must be #{KEY_SIZE} bytes, got #{bytes.bytesize}")
end
end
end
end

View File

@ -1,15 +0,0 @@
require "./lib_sodium"
module Cox
class SignSecretKey < Key
property bytes : Bytes
KEY_SIZE = LibSodium::SECRET_SIGN_SIZE
def initialize(@bytes : Bytes)
if bytes.bytesize != KEY_SIZE
raise ArgumentError.new("Secret key must be #{KEY_SIZE} bytes, got #{bytes.bytesize}")
end
end
end
end