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.
This commit is contained in:
parent
da8f97ae47
commit
0eb4a8991a
14
README.md
14
README.md
@ -9,7 +9,7 @@ Updated Crystal bindings for the [libsodium API](https://libsodium.gitbook.io/do
|
|||||||
- Public-Key Cryptography
|
- Public-Key Cryptography
|
||||||
- [x] Crypto Box Easy
|
- [x] Crypto Box Easy
|
||||||
- [ ] Sealed Box
|
- [ ] Sealed Box
|
||||||
- [x] Combined Signatures
|
- [ ] Combined Signatures
|
||||||
- [x] Detached Signatures
|
- [x] Detached Signatures
|
||||||
- [Secret-Key Cryptography](https://libsodium.gitbook.io/doc/secret-key_cryptography)
|
- [Secret-Key Cryptography](https://libsodium.gitbook.io/doc/secret-key_cryptography)
|
||||||
- Secret Box
|
- Secret Box
|
||||||
@ -89,13 +89,17 @@ String.new(decrypted) # => "Hello World!"
|
|||||||
```crystal
|
```crystal
|
||||||
message = "Hello World!"
|
message = "Hello World!"
|
||||||
|
|
||||||
signing_pair = Cox::SignKeyPair.new
|
secret_key = Cox::Sign::SecretKey.new
|
||||||
|
|
||||||
# Sign the message
|
# Sign the message
|
||||||
signature = Cox.sign_detached(message, signing_pair.secret)
|
signature = secret_key.sign_detached message
|
||||||
|
|
||||||
# And verify
|
# Send secret_key.public_key to the recipient
|
||||||
Cox.verify_detached(signature, message, signing_pair.public) # => true
|
|
||||||
|
public_key = Cox::Sign::PublicKey.new key_bytes
|
||||||
|
|
||||||
|
# raises Cox::Error::VerificationFailed on failure.
|
||||||
|
public_key.verify_detached message, signature
|
||||||
```
|
```
|
||||||
|
|
||||||
### Secret Key Encryption
|
### Secret Key Encryption
|
||||||
|
@ -9,7 +9,7 @@ describe Cox::SecretKey do
|
|||||||
decrypted = key.decrypt_easy encrypted, nonce
|
decrypted = key.decrypt_easy encrypted, nonce
|
||||||
message.should eq String.new(decrypted)
|
message.should eq String.new(decrypted)
|
||||||
|
|
||||||
expect_raises(Cox::DecryptionFailed) do
|
expect_raises(Cox::Error::DecryptionFailed) do
|
||||||
key.decrypt_easy "badmsgbadmsgbadmsgbadmsgbadmsg".to_slice, nonce
|
key.decrypt_easy "badmsgbadmsgbadmsgbadmsgbadmsg".to_slice, nonce
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
22
spec/cox/sign/secret_key.cr
Normal file
22
spec/cox/sign/secret_key.cr
Normal 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
|
@ -22,18 +22,4 @@ describe Cox do
|
|||||||
|
|
||||||
String.new(decrypted).should eq(data)
|
String.new(decrypted).should eq(data)
|
||||||
end
|
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
|
end
|
||||||
|
25
src/cox.cr
25
src/cox.cr
@ -2,14 +2,13 @@ require "random/secure"
|
|||||||
|
|
||||||
module Cox
|
module Cox
|
||||||
class Error < ::Exception
|
class Error < ::Exception
|
||||||
end
|
|
||||||
|
|
||||||
class VerificationFailed < Error
|
class VerificationFailed < Error
|
||||||
end
|
end
|
||||||
|
|
||||||
class DecryptionFailed < Error
|
class DecryptionFailed < Error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
require "./cox/**"
|
require "./cox/**"
|
||||||
|
|
||||||
@ -34,30 +33,10 @@ module Cox
|
|||||||
data_size = data_buffer.bytesize
|
data_size = data_buffer.bytesize
|
||||||
output_buffer = Bytes.new(data_buffer.bytesize - LibSodium::MAC_SIZE)
|
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
|
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
|
end
|
||||||
output_buffer
|
output_buffer
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
if Cox::LibSodium.sodium_init == -1
|
if Cox::LibSodium.sodium_init == -1
|
||||||
|
@ -108,7 +108,7 @@ module Cox
|
|||||||
|
|
||||||
fun crypto_sign_detached(
|
fun crypto_sign_detached(
|
||||||
signature_output : Pointer(LibC::UChar),
|
signature_output : Pointer(LibC::UChar),
|
||||||
signature_output_size : LibC::ULongLong,
|
signature_output_size : Pointer(LibC::ULongLong),
|
||||||
message : Pointer(LibC::UChar),
|
message : Pointer(LibC::UChar),
|
||||||
message_size : LibC::ULongLong,
|
message_size : LibC::ULongLong,
|
||||||
secret_key : Pointer(LibC::UChar)
|
secret_key : Pointer(LibC::UChar)
|
||||||
|
@ -48,7 +48,7 @@ module Cox
|
|||||||
|
|
||||||
def decrypt_easy(data : Bytes, nonce : Nonce) : Bytes
|
def decrypt_easy(data : Bytes, nonce : Nonce) : Bytes
|
||||||
output_size = data.bytesize - MAC_SIZE
|
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
|
output = Bytes.new output_size
|
||||||
decrypt_easy(data, output, nonce)
|
decrypt_easy(data, output, nonce)
|
||||||
end
|
end
|
||||||
@ -58,7 +58,7 @@ module Cox
|
|||||||
raise ArgumentError.new("dst.bytesize must be src.bytesize - MAC_SIZE, got #{dst.bytesize}")
|
raise ArgumentError.new("dst.bytesize must be src.bytesize - MAC_SIZE, got #{dst.bytesize}")
|
||||||
end
|
end
|
||||||
if LibSodium.crypto_secretbox_open_easy(dst, src, src.bytesize, nonce.to_slice, @bytes) != 0
|
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
|
end
|
||||||
dst
|
dst
|
||||||
end
|
end
|
||||||
|
28
src/cox/sign/public_key.cr
Normal file
28
src/cox/sign/public_key.cr
Normal 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
|
46
src/cox/sign/secret_key.cr
Normal file
46
src/cox/sign/secret_key.cr
Normal 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
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
Loading…
Reference in New Issue
Block a user