diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 996f015..5bc75e7 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -11,6 +11,7 @@ on: env: BUILD_TYPE: Debug LD_LIBRARY_PATH: /usr/local/lib + DYLD_LIBRARY_PATH: /usr/local/lib POSIX_PKG_CONFIG_PATH: ${{github.workspace}}/.config WIN_LIBOQS_INSTALL_PATH: C:\liboqs WIN_PKG_CONFIG_PATH: C:\Strawberry\c\lib\pkgconfig diff --git a/CHANGES.md b/CHANGES.md index 4113edb..deeab8c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,19 @@ +# Version 0.12.0 - January 15, 2025 + +- Fixes https://github.com/open-quantum-safe/liboqs-go/issues/44. The API that + NIST has introduced in [FIPS 204](https://csrc.nist.gov/pubs/fips/204/final) + for ML-DSA includes a context string of length >= 0. Added new API for + signing with a context string + - `func (sig *Signature) +SignWithCtxStr(message []byte, context []byte) ([]byte, error)` + - `func (sig *Signature) +VerifyWithCtxStr(message []byte, signature []byte, context []byte, +publicKey []byte) (bool, error)` +- Updated examples to use `ML-KEM` and `ML-DSA` as the defaults +- Removed the `oqs.rand` package and moved the `RandomBytes` functions from +`oqs.rand` to the main `oqs` package to avoid warnings about linking liboqs +twice + # Version 0.10.0 - March 27, 2024 - Bumped Go version to 1.21 diff --git a/LICENSE b/LICENSE index a945cbb..80dd311 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2024 Open Quantum Safe +Copyright (c) 2019-2025 Open Quantum Safe Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 36d46cf..d49b029 100644 --- a/README.md +++ b/README.md @@ -73,9 +73,8 @@ The project contains the following files and directories: Please note that on some platforms not all algorithms are supported: -- macOS/Darwin: The Rainbow and Classic-McEliece algorithm families as well as - HQC-256 do not work. -- Windows: The Rainbow and Classic-McEliece algorithm families do not work. +- macOS/Darwin: No known issues as of liboqs-0.12.0 +- Windows: No known issues as of liboqs-0.12.0 --- diff --git a/RELEASE.md b/RELEASE.md index 4baae0a..a5bd2eb 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,4 +1,4 @@ -# liboqs-go version 0.10.0 +# liboqs-go version 0.12.0 --- @@ -24,12 +24,12 @@ See in particular limitations on intended use. ## Release notes -This release of liboqs-go was released on March 27, 2024. Its release page on -GitHub is https://github.com/open-quantum-safe/liboqs-go/releases/tag/0.10.0. +This release of liboqs-go was released on January 15, 2025. Its release page on +GitHub is https://github.com/open-quantum-safe/liboqs-go/releases/tag/0.12.0. --- ## What's New -This is the 13th release of liboqs-go. For a list of changes see +This is the 14th release of liboqs-go. For a list of changes see [CHANGES.md](https://github.com/open-quantum-safe/liboqs-go/blob/main/CHANGES.md). diff --git a/examples/client_server_kem/server/server_kem.go b/examples/client_server_kem/server/server_kem.go index 2898a32..b4c26ad 100644 --- a/examples/client_server_kem/server/server_kem.go +++ b/examples/client_server_kem/server/server_kem.go @@ -43,7 +43,7 @@ func main() { os.Exit(1) } port := os.Args[1] - kemName := "Kyber512" + kemName := "ML-KEM-512" if len(os.Args) > 2 { kemName = os.Args[2] } @@ -116,8 +116,9 @@ func handleConnection(conn net.Conn, kemName string) { Details().LengthCiphertext) + " bytes, but instead wrote " + fmt.Sprintf("%v", n))) } + // First connection is #1 log.Printf("\nConnection #%d - server shared secret:\n% X ... % X\n\n", - counter.Val(), sharedSecretServer[0:8], + counter.Val()+1, sharedSecretServer[0:8], sharedSecretServer[len(sharedSecretServer)-8:]) // Increment the connection number diff --git a/examples/kem/kem.go b/examples/kem/kem.go index ed804e0..013133c 100644 --- a/examples/kem/kem.go +++ b/examples/kem/kem.go @@ -14,7 +14,7 @@ func main() { fmt.Println("Enabled KEMs:") fmt.Println(oqs.EnabledKEMs()) - kemName := "Kyber512" + kemName := "ML-KEM-512" client := oqs.KeyEncapsulation{} defer client.Clean() // clean up even in case of panic diff --git a/examples/rand/rand.go b/examples/rand/rand.go index c966388..781ff16 100644 --- a/examples/rand/rand.go +++ b/examples/rand/rand.go @@ -7,7 +7,6 @@ import ( "runtime" "github.com/open-quantum-safe/liboqs-go/oqs" - oqsrand "github.com/open-quantum-safe/liboqs-go/oqs/rand" // RNG support ) // CustomRNG provides a (trivial) custom random number generator; the memory is @@ -22,20 +21,20 @@ func CustomRNG(randomArray []byte, bytesToRead int) { func main() { fmt.Println("liboqs version: " + oqs.LiboqsVersion()) - if err := oqsrand.RandomBytesSwitchAlgorithm("system"); err != nil { + if err := oqs.RandomBytesSwitchAlgorithm("system"); err != nil { log.Fatal(err) } - fmt.Printf("%18s% X\n", "System (default): ", oqsrand.RandomBytes(32)) - if err := oqsrand.RandomBytesCustomAlgorithm(CustomRNG); err != nil { + fmt.Printf("%18s% X\n", "System (default): ", oqs.RandomBytes(32)) + if err := oqs.RandomBytesCustomAlgorithm(CustomRNG); err != nil { log.Fatal(err) } - fmt.Printf("%-18s% X\n", "Custom RNG: ", oqsrand.RandomBytes(32)) + fmt.Printf("%-18s% X\n", "Custom RNG: ", oqs.RandomBytes(32)) // We do not yet support OpenSSL under Windows if runtime.GOOS != "windows" { - if err := oqsrand.RandomBytesSwitchAlgorithm("OpenSSL"); err != nil { + if err := oqs.RandomBytesSwitchAlgorithm("OpenSSL"); err != nil { log.Fatal(err) } - fmt.Printf("%-18s% X\n", "OpenSSL: ", oqsrand.RandomBytes(32)) + fmt.Printf("%-18s% X\n", "OpenSSL: ", oqs.RandomBytes(32)) } } diff --git a/examples/sig/sig.go b/examples/sig/sig.go index 4d8d22f..da1c580 100644 --- a/examples/sig/sig.go +++ b/examples/sig/sig.go @@ -13,7 +13,7 @@ func main() { fmt.Println("Enabled signatures:") fmt.Println(oqs.EnabledSigs()) - sigName := "Dilithium2" + sigName := "ML-DSA-44" signer := oqs.Signature{} defer signer.Clean() // clean up even in case of panic @@ -32,7 +32,10 @@ func main() { fmt.Printf("\nSigner public key:\n% X ... % X\n", pubKey[0:8], pubKey[len(pubKey)-8:]) - signature, _ := signer.Sign(msg) + signature, err := signer.Sign(msg) + if err != nil { + log.Fatal(err) + } fmt.Printf("\nSignature:\n% X ... % X\n", signature[0:8], signature[len(signature)-8:]) diff --git a/oqs/cfuncs.go b/oqs/cfuncs.go new file mode 100644 index 0000000..a676105 --- /dev/null +++ b/oqs/cfuncs.go @@ -0,0 +1,13 @@ +package oqs + +// C callbacks, DO NOT CHANGE + +/* +#include +#include +void randAlgorithmPtr_cgo(uint8_t* random_array, size_t bytes_to_read) { + void randAlgorithmPtr(uint8_t*, size_t); + randAlgorithmPtr(random_array, bytes_to_read); +} +*/ +import "C" diff --git a/oqs/oqs.go b/oqs/oqs.go index 7e5fc1d..78762aa 100644 --- a/oqs/oqs.go +++ b/oqs/oqs.go @@ -4,6 +4,8 @@ package oqs // import "github.com/open-quantum-safe/liboqs-go/oqs" /* #cgo pkg-config: liboqs-go #include +typedef void (*rand_algorithm_ptr)(uint8_t*, size_t); +void randAlgorithmPtr_cgo(uint8_t*, size_t); */ import "C" @@ -95,27 +97,36 @@ func init() { // KeyEncapsulationDetails defines the KEM algorithm details. type KeyEncapsulationDetails struct { + Name string + Version string ClaimedNISTLevel int IsINDCCA bool - LengthCiphertext int LengthPublicKey int LengthSecretKey int + LengthCiphertext int LengthSharedSecret int - Name string - Version string } // String converts the KEM algorithm details to a string representation. Use // this method to pretty-print the KEM algorithm details, e.g. // fmt.Println(client.Details()). func (kemDetails KeyEncapsulationDetails) String() string { - return fmt.Sprintf("Name: %s\nVersion: %s\nClaimed NIST level: %d\n"+ - "Is IND_CCA: %v\nLength public key (bytes): %d\nLength secret key ("+ - "bytes): %d\nLength ciphertext (bytes): %d\nLength shared secret ("+ - "bytes): %d", kemDetails.Name, - kemDetails.Version, kemDetails.ClaimedNISTLevel, kemDetails.IsINDCCA, - kemDetails.LengthPublicKey, kemDetails.LengthSecretKey, - kemDetails.LengthCiphertext, kemDetails.LengthSharedSecret) + return fmt.Sprintf("Name: %s\n"+ + "Version: %s\n"+ + "Claimed NIST level: %d\n"+ + "Is IND_CCA: %v\n"+ + "Length public key (bytes): %d\n"+ + "Length secret key (bytes): %d\n"+ + "Length ciphertext (bytes): %d\n"+ + "Length shared secret (bytes): %d", + kemDetails.Name, + kemDetails.Version, + kemDetails.ClaimedNISTLevel, + kemDetails.IsINDCCA, + kemDetails.LengthPublicKey, + kemDetails.LengthSecretKey, + kemDetails.LengthCiphertext, + kemDetails.LengthSharedSecret) } // KeyEncapsulation defines the KEM main data structure. @@ -170,9 +181,12 @@ func (kem *KeyEncapsulation) GenerateKeyPair() ([]byte, error) { publicKey := make([]byte, kem.algDetails.LengthPublicKey) kem.secretKey = make([]byte, kem.algDetails.LengthSecretKey) - rv := C.OQS_KEM_keypair(kem.kem, + rv := C.OQS_KEM_keypair( + kem.kem, (*C.uint8_t)(unsafe.Pointer(&publicKey[0])), - (*C.uint8_t)(unsafe.Pointer(&kem.secretKey[0]))) + (*C.uint8_t)(unsafe.Pointer(&kem.secretKey[0])), + ) + if rv != C.OQS_SUCCESS { return nil, errors.New("can not generate keypair") } @@ -197,10 +211,12 @@ func (kem *KeyEncapsulation) EncapSecret(publicKey []byte) (ciphertext, ciphertext = make([]byte, kem.algDetails.LengthCiphertext) sharedSecret = make([]byte, kem.algDetails.LengthSharedSecret) - rv := C.OQS_KEM_encaps(kem.kem, + rv := C.OQS_KEM_encaps( + kem.kem, (*C.uint8_t)(unsafe.Pointer(&ciphertext[0])), (*C.uint8_t)(unsafe.Pointer(&sharedSecret[0])), - (*C.uint8_t)(unsafe.Pointer(&publicKey[0]))) + (*C.uint8_t)(unsafe.Pointer(&publicKey[0])), + ) if rv != C.OQS_SUCCESS { return nil, nil, errors.New("can not encapsulate secret") @@ -222,10 +238,12 @@ func (kem *KeyEncapsulation) DecapSecret(ciphertext []byte) ([]byte, error) { } sharedSecret := make([]byte, kem.algDetails.LengthSharedSecret) - rv := C.OQS_KEM_decaps(kem.kem, + rv := C.OQS_KEM_decaps( + kem.kem, (*C.uint8_t)(unsafe.Pointer(&sharedSecret[0])), (*C.uchar)(unsafe.Pointer(&ciphertext[0])), - (*C.uint8_t)(unsafe.Pointer(&kem.secretKey[0]))) + (*C.uint8_t)(unsafe.Pointer(&kem.secretKey[0])), + ) if rv != C.OQS_SUCCESS { return nil, errors.New("can not decapsulate secret") @@ -313,24 +331,35 @@ func init() { // SignatureDetails defines the signature algorithm details. type SignatureDetails struct { + Name string + Version string ClaimedNISTLevel int IsEUFCMA bool + SigWithCtxSupport bool LengthPublicKey int LengthSecretKey int MaxLengthSignature int - Name string - Version string } // String converts the signature algorithm details to a string representation. // Use this method to pretty-print the signature algorithm details, e.g. // fmt.Println(signer.Details()). func (sigDetails SignatureDetails) String() string { - return fmt.Sprintf("Name: %s\nVersion: %s\nClaimed NIST level: %d\n"+ - "Is EUF_CMA: %v\nLength public key (bytes): %d\nLength secret key ("+ - "bytes): %d\nMaximum length signature (bytes): %d", sigDetails.Name, - sigDetails.Version, sigDetails.ClaimedNISTLevel, sigDetails.IsEUFCMA, - sigDetails.LengthPublicKey, sigDetails.LengthSecretKey, + return fmt.Sprintf("Name: %s\n"+ + "Version: %s\n"+ + "Claimed NIST level: %d\n"+ + "Is EUF_CMA: %v\n"+ + "Supports context string: %v\n"+ + "Length public key (bytes): %d\n"+ + "Length secret key (bytes): %d\n"+ + "Maximum length signature (bytes): %d", + sigDetails.Name, + sigDetails.Version, + sigDetails.ClaimedNISTLevel, + sigDetails.IsEUFCMA, + sigDetails.SigWithCtxSupport, + sigDetails.LengthPublicKey, + sigDetails.LengthSecretKey, sigDetails.MaxLengthSignature) } @@ -370,9 +399,11 @@ func (sig *Signature) Init(algName string, secretKey []byte) error { sig.algDetails.Version = C.GoString(sig.sig.alg_version) sig.algDetails.ClaimedNISTLevel = int(sig.sig.claimed_nist_level) sig.algDetails.IsEUFCMA = bool(sig.sig.euf_cma) + sig.algDetails.SigWithCtxSupport = bool(sig.sig.sig_with_ctx_support) sig.algDetails.LengthPublicKey = int(sig.sig.length_public_key) sig.algDetails.LengthSecretKey = int(sig.sig.length_secret_key) sig.algDetails.MaxLengthSignature = int(sig.sig.length_signature) + return nil } @@ -389,9 +420,12 @@ func (sig *Signature) GenerateKeyPair() ([]byte, error) { publicKey := make([]byte, sig.algDetails.LengthPublicKey) sig.secretKey = make([]byte, sig.algDetails.LengthSecretKey) - rv := C.OQS_SIG_keypair(sig.sig, + rv := C.OQS_SIG_keypair( + sig.sig, (*C.uint8_t)(unsafe.Pointer(&publicKey[0])), - (*C.uint8_t)(unsafe.Pointer(&sig.secretKey[0]))) + (*C.uint8_t)(unsafe.Pointer(&sig.secretKey[0])), + ) + if rv != C.OQS_SUCCESS { return nil, errors.New("can not generate keypair") } @@ -413,10 +447,46 @@ func (sig *Signature) Sign(message []byte) ([]byte, error) { signature := make([]byte, sig.algDetails.MaxLengthSignature) var lenSig uint64 - rv := C.OQS_SIG_sign(sig.sig, (*C.uint8_t)(unsafe.Pointer(&signature[0])), + rv := C.OQS_SIG_sign( + sig.sig, + (*C.uint8_t)(unsafe.Pointer(&signature[0])), (*C.size_t)(unsafe.Pointer(&lenSig)), (*C.uint8_t)(unsafe.Pointer(&message[0])), - C.size_t(len(message)), (*C.uint8_t)(unsafe.Pointer(&sig.secretKey[0]))) + C.size_t(len(message)), + (*C.uint8_t)(unsafe.Pointer(&sig.secretKey[0])), + ) + + if rv != C.OQS_SUCCESS { + return nil, errors.New("can not sign message") + } + + return signature[:lenSig], nil +} + +// Sign signs a message with context string and returns the corresponding +// signature. +func (sig *Signature) SignWithCtxStr(message []byte, context []byte) ([]byte, error) { + if len(context) > 0 && !sig.algDetails.SigWithCtxSupport { + return nil, errors.New("can not sign message with context string") + } + + if len(sig.secretKey) != sig.algDetails.LengthSecretKey { + return nil, errors.New("incorrect secret key length, make sure you " + + "specify one in Init() or run GenerateKeyPair()") + } + + signature := make([]byte, sig.algDetails.MaxLengthSignature) + var lenSig uint64 + rv := C.OQS_SIG_sign_with_ctx_str( + sig.sig, + (*C.uint8_t)(unsafe.Pointer(&signature[0])), + (*C.size_t)(unsafe.Pointer(&lenSig)), + (*C.uint8_t)(unsafe.Pointer(&message[0])), + C.size_t(len(message)), + (*C.uint8_t)(unsafe.Pointer(&context[0])), + C.size_t(len(context)), + (*C.uint8_t)(unsafe.Pointer(&sig.secretKey[0])), + ) if rv != C.OQS_SUCCESS { return nil, errors.New("can not sign message") @@ -438,9 +508,54 @@ func (sig *Signature) Verify(message []byte, signature []byte, return false, errors.New("incorrect signature size") } - rv := C.OQS_SIG_verify(sig.sig, (*C.uint8_t)(unsafe.Pointer(&message[0])), - C.size_t(len(message)), (*C.uint8_t)(unsafe.Pointer(&signature[0])), - C.size_t(len(signature)), (*C.uint8_t)(unsafe.Pointer(&publicKey[0]))) + rv := C.OQS_SIG_verify( + sig.sig, + (*C.uint8_t)(unsafe.Pointer(&message[0])), + C.size_t(len(message)), + (*C.uint8_t)(unsafe.Pointer(&signature[0])), + C.size_t(len(signature)), + (*C.uint8_t)(unsafe.Pointer(&publicKey[0])), + ) + + if rv != C.OQS_SUCCESS { + return false, nil + } + + return true, nil +} + +// Verify verifies the validity of a signed message with context string, +// returning true if the signature is valid, and false otherwise. +func (sig *Signature) VerifyWithCtxStr( + message []byte, + signature []byte, + context []byte, + publicKey []byte, +) (bool, error) { + if len(context) > 0 && !sig.algDetails.SigWithCtxSupport { + return false, errors.New("can not sign message with context string") + } + + if len(publicKey) != sig.algDetails.LengthPublicKey { + return false, errors.New("incorrect public key length") + } + + if len(signature) > sig.algDetails.MaxLengthSignature { + return false, errors.New("incorrect signature size") + } + + rv := C.OQS_SIG_verify_with_ctx_str( + sig.sig, + (*C.uint8_t)( + unsafe.Pointer(&message[0]), + ), + C.size_t(len(message)), + (*C.uint8_t)(unsafe.Pointer(&signature[0])), + C.size_t(len(signature)), + (*C.uint8_t)(unsafe.Pointer(&context[0])), + C.size_t(len(context)), + (*C.uint8_t)(unsafe.Pointer(&publicKey[0])), + ) if rv != C.OQS_SUCCESS { return false, nil @@ -460,3 +575,77 @@ func (sig *Signature) Clean() { } /**************** END Signature ****************/ + +/**************** Randomness ****************/ + +/**************** Callbacks ****************/ + +// randAlgorithmPtrCallback is a global RNG algorithm callback set by +// RandomBytesCustomAlgorithm. +var randAlgorithmPtrCallback func([]byte, int) + +// randAlgorithmPtr is automatically invoked by RandomBytesCustomAlgorithm. When +// invoked, the memory is provided by the caller, i.e. RandomBytes or +// RandomBytesInPlace. +// +//export randAlgorithmPtr +func randAlgorithmPtr(randomArray *C.uint8_t, bytesToRead C.size_t) { + // TODO optimize the copying if possible! + result := make([]byte, int(bytesToRead)) + randAlgorithmPtrCallback(result, int(bytesToRead)) + p := unsafe.Pointer(randomArray) + for _, v := range result { + *(*C.uint8_t)(p) = C.uint8_t(v) + p = unsafe.Pointer(uintptr(p) + 1) + } +} + +/**************** END Callbacks ****************/ + +// RandomBytes generates bytesToRead random bytes. This implementation uses +// either the default RNG algorithm ("system"), or whichever algorithm has been +// selected by RandomBytesSwitchAlgorithm. +func RandomBytes(bytesToRead int) []byte { + result := make([]byte, bytesToRead) + C.OQS_randombytes((*C.uint8_t)(unsafe.Pointer(&result[0])), + C.size_t(bytesToRead)) + return result +} + +// RandomBytesInPlace generates bytesToRead random bytes. This implementation +// uses either the default RNG algorithm ("system"), or whichever algorithm has +// been selected by RandomBytesSwitchAlgorithm. If bytesToRead exceeds the size +// of randomArray, only len(randomArray) bytes are read. +func RandomBytesInPlace(randomArray []byte, bytesToRead int) { + if bytesToRead > len(randomArray) { + bytesToRead = len(randomArray) + } + C.OQS_randombytes((*C.uint8_t)(unsafe.Pointer(&randomArray[0])), + C.size_t(bytesToRead)) +} + +// RandomBytesSwitchAlgorithm switches the core OQS_randombytes to use the +// specified algorithm. Possible values are "system" and "OpenSSL". +// See liboqs header for more details. +func RandomBytesSwitchAlgorithm(algName string) error { + if C.OQS_randombytes_switch_algorithm(C.CString(algName)) != C.OQS_SUCCESS { + return errors.New("can not switch to \"" + algName + "\" algorithm") + } + return nil +} + +// RandomBytesCustomAlgorithm switches RandomBytes to use the given function. +// This allows additional custom RNGs besides the provided ones. The provided +// RNG function must have the same signature as RandomBytesInPlace, +// i.e. func([]byte, int). +func RandomBytesCustomAlgorithm(fun func([]byte, int)) error { + if fun == nil { + return errors.New("the RNG algorithm callback can not be nil") + } + randAlgorithmPtrCallback = fun + C.OQS_randombytes_custom_algorithm( + (C.rand_algorithm_ptr)(unsafe.Pointer(C.randAlgorithmPtr_cgo))) + return nil +} + +/**************** END Randomness ****************/ diff --git a/oqs/rand/cfuncs.go b/oqs/rand/cfuncs.go deleted file mode 100644 index 7418165..0000000 --- a/oqs/rand/cfuncs.go +++ /dev/null @@ -1,13 +0,0 @@ -package rand - -// C callbacks, DO NOT CHANGE - -/* -#include -#include -void algorithmPtr_cgo(uint8_t* random_array, size_t bytes_to_read) { - void algorithmPtr(uint8_t*, size_t); - algorithmPtr(random_array, bytes_to_read); -} -*/ -import "C" diff --git a/oqs/rand/rand.go b/oqs/rand/rand.go deleted file mode 100644 index 7073056..0000000 --- a/oqs/rand/rand.go +++ /dev/null @@ -1,89 +0,0 @@ -// Package rand provides support for various RNG-related functions. -package rand // import "github.com/open-quantum-safe/liboqs-go/oqs/rand" - -/**************** Callbacks ****************/ - -/* -#cgo pkg-config: liboqs-go -#include -typedef void (*algorithm_ptr)(uint8_t*, size_t); -void algorithmPtr_cgo(uint8_t*, size_t); -*/ -import "C" - -import ( - "errors" - "unsafe" -) - -// algorithmPtrCallback is a global RNG algorithm callback set by -// RandomBytesCustomAlgorithm. -var algorithmPtrCallback func([]byte, int) - -// algorithmPtr is automatically invoked by RandomBytesCustomAlgorithm. When -// invoked, the memory is provided by the caller, i.e. RandomBytes or -// RandomBytesInPlace. -// -//export algorithmPtr -func algorithmPtr(randomArray *C.uint8_t, bytesToRead C.size_t) { - // TODO optimize the copying if possible! - result := make([]byte, int(bytesToRead)) - algorithmPtrCallback(result, int(bytesToRead)) - p := unsafe.Pointer(randomArray) - for _, v := range result { - *(*C.uint8_t)(p) = C.uint8_t(v) - p = unsafe.Pointer(uintptr(p) + 1) - } -} - -/**************** END Callbacks ****************/ - -/**************** Randomness ****************/ - -// RandomBytes generates bytesToRead random bytes. This implementation uses -// either the default RNG algorithm ("system"), or whichever algorithm has been -// selected by RandomBytesSwitchAlgorithm. -func RandomBytes(bytesToRead int) []byte { - result := make([]byte, bytesToRead) - C.OQS_randombytes((*C.uint8_t)(unsafe.Pointer(&result[0])), - C.size_t(bytesToRead)) - return result -} - -// RandomBytesInPlace generates bytesToRead random bytes. This implementation -// uses either the default RNG algorithm ("system"), or whichever algorithm has -// been selected by RandomBytesSwitchAlgorithm. If bytesToRead exceeds the size -// of randomArray, only len(randomArray) bytes are read. -func RandomBytesInPlace(randomArray []byte, bytesToRead int) { - if bytesToRead > len(randomArray) { - bytesToRead = len(randomArray) - } - C.OQS_randombytes((*C.uint8_t)(unsafe.Pointer(&randomArray[0])), - C.size_t(bytesToRead)) -} - -// RandomBytesSwitchAlgorithm switches the core OQS_randombytes to use the -// specified algorithm. Possible values are "system" and "OpenSSL". -// See liboqs header for more details. -func RandomBytesSwitchAlgorithm(algName string) error { - if C.OQS_randombytes_switch_algorithm(C.CString(algName)) != C.OQS_SUCCESS { - return errors.New("can not switch to \"" + algName + "\" algorithm") - } - return nil -} - -// RandomBytesCustomAlgorithm switches RandomBytes to use the given function. -// This allows additional custom RNGs besides the provided ones. The provided -// RNG function must have the same signature as RandomBytesInPlace, -// i.e. func([]byte, int). -func RandomBytesCustomAlgorithm(fun func([]byte, int)) error { - if fun == nil { - return errors.New("the RNG algorithm callback can not be nil") - } - algorithmPtrCallback = fun - C.OQS_randombytes_custom_algorithm( - (C.algorithm_ptr)(unsafe.Pointer(C.algorithmPtr_cgo))) - return nil -} - -/**************** END Randomness ****************/ diff --git a/oqstests/kem_test.go b/oqstests/kem_test.go index 2976a89..a35a9fa 100644 --- a/oqstests/kem_test.go +++ b/oqstests/kem_test.go @@ -9,14 +9,13 @@ import ( "testing" "github.com/open-quantum-safe/liboqs-go/oqs" - "github.com/open-quantum-safe/liboqs-go/oqs/rand" ) // disabledKEMPatterns lists KEMs for which unit testing is disabled var disabledKEMPatterns []string // noThreadKEMPatterns lists KEMs that have issues running in a separate thread -var noThreadKEMPatterns = []string{"LEDAcryptKEM-LT52", "HQC-256"} +var noThreadKEMPatterns = []string{} // wgKEMCorrectness groups goroutines and blocks the caller until all goroutines finish. var wgKEMCorrectness sync.WaitGroup @@ -41,7 +40,7 @@ func testKEMCorrectness(kemName string, threading bool, t *testing.T) { sharedSecretClient, _ := client.DecapSecret(ciphertext) if !bytes.Equal(sharedSecretClient, sharedSecretServer) { // t.Errorf is thread-safe - t.Errorf(kemName + ": shared secrets do not coincide") + t.Errorf("%s: shared secrets do not coincide", kemName) } } @@ -59,11 +58,11 @@ func testKEMWrongCiphertext(kemName string, threading bool, t *testing.T) { _ = server.Init(kemName, nil) clientPublicKey, _ := client.GenerateKeyPair() ciphertext, sharedSecretServer, _ := server.EncapSecret(clientPublicKey) - wrongCiphertext := rand.RandomBytes(len(ciphertext)) + wrongCiphertext := oqs.RandomBytes(len(ciphertext)) sharedSecretClient, _ := client.DecapSecret(wrongCiphertext) if bytes.Equal(sharedSecretClient, sharedSecretServer) { // t.Errorf is thread-safe - t.Errorf(kemName + ": shared secrets should not coincide") + t.Errorf("%s: shared secrets should not coincide", kemName) } } @@ -71,7 +70,7 @@ func testKEMWrongCiphertext(kemName string, threading bool, t *testing.T) { func TestKeyEncapsulationCorrectness(t *testing.T) { // Disable some KEMs in macOS/OSX if runtime.GOOS == "darwin" { - disabledKEMPatterns = []string{"Classic-McEliece", "HQC-256"} + disabledKEMPatterns = []string{} } // Disable some KEMs in OpenIndiana if runtime.GOOS == "illumos" { @@ -79,7 +78,7 @@ func TestKeyEncapsulationCorrectness(t *testing.T) { } // Disable some KEMs in Windows if runtime.GOOS == "windows" { - disabledKEMPatterns = []string{"Classic-McEliece"} + disabledKEMPatterns = []string{} } // First test KEMs that belong to noThreadKEMPatterns[] in the main // goroutine, due to issues with stack size being too small in macOS or @@ -113,7 +112,7 @@ func TestKeyEncapsulationCorrectness(t *testing.T) { func TestKeyEncapsulationWrongCiphertext(t *testing.T) { // disable some KEMs in macOS/OSX if runtime.GOOS == "darwin" { - disabledKEMPatterns = []string{"Classic-McEliece", "HQC-256"} + disabledKEMPatterns = []string{} } // Disable some KEMs in OpenIndiana if runtime.GOOS == "illumos" { @@ -121,7 +120,7 @@ func TestKeyEncapsulationWrongCiphertext(t *testing.T) { } // Disable some KEMs in Windows if runtime.GOOS == "windows" { - disabledKEMPatterns = []string{"Classic-McEliece"} + disabledKEMPatterns = []string{} } // First test KEMs that belong to noThreadKEMPatterns[] in the main // goroutine, due to issues with stack size being too small in macOS or diff --git a/oqstests/sig_test.go b/oqstests/sig_test.go index 1dbda39..ea5b337 100644 --- a/oqstests/sig_test.go +++ b/oqstests/sig_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/open-quantum-safe/liboqs-go/oqs" - "github.com/open-quantum-safe/liboqs-go/oqs/rand" ) // disabledSigPatterns lists sigs for which unit testing is disabled @@ -42,7 +41,34 @@ func testSigCorrectness(sigName string, msg []byte, threading bool, t *testing.T isValid, _ := verifier.Verify(msg, signature, pubKey) if !isValid { // t.Errorf is thread-safe - t.Errorf(sigName + ": signature verification failed") + t.Errorf("%s: signature verification failed", sigName) + } +} + +// testSigCorrectnessWithCtxStr tests a specific signature with context string. +func testSigCorrectnessWithCtxStr(sigName string, msg []byte, threading bool, t *testing.T) { + if threading == true { + defer wgSigCorrectness.Done() + } + var signer, verifier oqs.Signature + defer signer.Clean() + defer verifier.Clean() + + // Ignore potential errors everywhere + _ = signer.Init(sigName, nil) + if !signer.Details().SigWithCtxSupport { + return + } + + log.Println("Correctness with context string - ", sigName) // thread-safe + // Ignore potential errors everywhere + _ = verifier.Init(sigName, nil) + pubKey, _ := signer.GenerateKeyPair() + signature, _ := signer.Sign(msg) + isValid, _ := verifier.Verify(msg, signature, pubKey) + if !isValid { + // t.Errorf is thread-safe + t.Errorf("%s: signature verification failed", sigName) } } @@ -60,11 +86,11 @@ func testSigWrongSignature(sigName string, msg []byte, threading bool, t *testin _ = verifier.Init(sigName, nil) pubKey, _ := signer.GenerateKeyPair() signature, _ := signer.Sign(msg) - wrongSignature := rand.RandomBytes(len(signature)) + wrongSignature := oqs.RandomBytes(len(signature)) isValid, _ := verifier.Verify(msg, wrongSignature, pubKey) if isValid { // t.Errorf is thread-safe - t.Errorf(sigName + ": signature verification should have failed") + t.Errorf("%s: signature verification should have failed", sigName) } } @@ -81,12 +107,12 @@ func testSigWrongPublicKey(sigName string, msg []byte, threading bool, t *testin _ = signer.Init(sigName, nil) _ = verifier.Init(sigName, nil) pubKey, _ := signer.GenerateKeyPair() - wrongPubKey := rand.RandomBytes(len(pubKey)) + wrongPubKey := oqs.RandomBytes(len(pubKey)) signature, _ := signer.Sign(msg) isValid, _ := verifier.Verify(msg, signature, wrongPubKey) if isValid { // t.Errorf is thread-safe - t.Errorf(sigName + ": signature verification should have failed") + t.Errorf("%s: signature verification should have failed", sigName) } } @@ -94,11 +120,11 @@ func testSigWrongPublicKey(sigName string, msg []byte, threading bool, t *testin func TestSignatureCorrectness(t *testing.T) { // Disable some sigs in macOS/OSX if runtime.GOOS == "darwin" { - disabledSigPatterns = []string{"Rainbow-III", "Rainbow-V"} + disabledSigPatterns = []string{} } // Disable some sigs in Windows if runtime.GOOS == "windows" { - disabledSigPatterns = []string{"Rainbow-V"} + disabledSigPatterns = []string{} } msg := []byte("This is our favourite message to sign") // First test sigs that belong to noThreadSigPatterns[] in the main @@ -129,16 +155,55 @@ func TestSignatureCorrectness(t *testing.T) { wgSigCorrectness.Wait() } +// TestSignatureCorrectnessWithCtxStr tests all enabled signatures that support context strings. +func TestSignatureCorrectnessWithCtxStr(t *testing.T) { + // Disable some sigs in macOS/OSX + if runtime.GOOS == "darwin" { + disabledSigPatterns = []string{} + } + // Disable some sigs in Windows + if runtime.GOOS == "windows" { + disabledSigPatterns = []string{} + } + msg := []byte("This is our favourite message to sign") + // First test sigs that belong to noThreadSigPatterns[] in the main + // goroutine, due to issues with stack size being too small in macOS or + // Windows + cnt := 0 + for _, sigName := range oqs.EnabledSigs() { + if stringMatchSlice(sigName, disabledSigPatterns) { + cnt++ + continue + } + // Issues with stack size being too small + if stringMatchSlice(sigName, noThreadSigPatterns) { + cnt++ + testSigCorrectnessWithCtxStr(sigName, msg, false, t) + } + } + // Test the remaining sigs in separate goroutines + wgSigCorrectness.Add(len(oqs.EnabledSigs()) - cnt) + for _, sigName := range oqs.EnabledSigs() { + if stringMatchSlice(sigName, disabledSigPatterns) { + continue + } + if !stringMatchSlice(sigName, noThreadSigPatterns) { + go testSigCorrectnessWithCtxStr(sigName, msg, true, t) + } + } + wgSigCorrectness.Wait() +} + // TestSignatureWrongSignature tests the wrong signature regime of all enabled // signatures. func TestSignatureWrongSignature(t *testing.T) { // Disable some sigs in macOS/OSX if runtime.GOOS == "darwin" { - disabledSigPatterns = []string{"Rainbow-III", "Rainbow-V"} + disabledSigPatterns = []string{} } // Disable some sigs in Windows if runtime.GOOS == "windows" { - disabledSigPatterns = []string{"Rainbow-V"} + disabledSigPatterns = []string{} } msg := []byte("This is our favourite message to sign") // First test sigs that belong to noThreadSigPatterns[] in the main @@ -175,11 +240,11 @@ func TestSignatureWrongSignature(t *testing.T) { func TestSignatureWrongPublicKey(t *testing.T) { // Disable some sigs in macOS/OSX if runtime.GOOS == "darwin" { - disabledSigPatterns = []string{"Rainbow-III", "Rainbow-V"} + disabledSigPatterns = []string{} } // Disable some sigs in Windows if runtime.GOOS == "windows" { - disabledSigPatterns = []string{"Rainbow-V"} + disabledSigPatterns = []string{} } msg := []byte("This is our favourite message to sign") // First test sigs that belong to noThreadSigPatterns[] in the main