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
					
				
					 12 changed files with 114 additions and 103 deletions
				
			
		
							
								
								
									
										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 | ||||||
|  |  | ||||||
							
								
								
									
										31
									
								
								src/cox.cr
									
										
									
									
									
								
							
							
						
						
									
										31
									
								
								src/cox.cr
									
										
									
									
									
								
							|  | @ -2,12 +2,11 @@ require "random/secure" | ||||||
| 
 | 
 | ||||||
| module Cox | module Cox | ||||||
|   class Error < ::Exception |   class Error < ::Exception | ||||||
|   end |     class VerificationFailed < Error | ||||||
|  |     end | ||||||
| 
 | 
 | ||||||
|   class VerificationFailed < Error |     class DecryptionFailed < Error | ||||||
|   end |     end | ||||||
| 
 |  | ||||||
|   class DecryptionFailed < Error |  | ||||||
|   end |   end | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
|  | @ -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…
	
	Add table
		
		Reference in a new issue
	
	 Didactic Drunk
						Didactic Drunk