Skip to content

Commit 93a3331

Browse files
authored
feat: introduce userauth internal package for anon creds support (#1766)
## Checklist - [x] I have read the [contribution guidelines](https://github.com/ooni/probe-cli/blob/master/CONTRIBUTING.md) - [x] reference issue for this pull request: - [ ] if you changed anything related to how experiments work and you need to reflect these changes in the ooni/spec repository, please link to the related ooni/spec pull request: <!-- add URL here --> - [ ] if you changed code inside an experiment, make sure you bump its version number <!-- Reminder: Location of the issue tracker: https://github.com/ooni/probe --> ## Description This diff introduces a new userauth package which makes use of the build outputs in `ooniprobe-rs` to make FFI calls using CGO
1 parent b343e2e commit 93a3331

3 files changed

Lines changed: 203 additions & 0 deletions

File tree

internal/userauth/doc.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Package userauth provides Go bindings for the OONI Probe anonymous credentials library.
2+
//
3+
// This package uses CGo to call into a Rust library that handles cryptographic
4+
// operations for user registration and measurement submission
5+
6+
package userauth
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#include <stdarg.h>
2+
#include <stdbool.h>
3+
#include <stdint.h>
4+
#include <stdlib.h>
5+
6+
typedef struct ClientResponse {
7+
char *json;
8+
char *error;
9+
} ClientResponse;
10+
11+
/**
12+
* Free memory allocated by ClientResponse
13+
*
14+
* # Safety
15+
* This function must be called exactly once for each ClientResponse
16+
* returned by other FFI functions to avoid memory leaks.
17+
*/
18+
void client_response_free(struct ClientResponse response);
19+
20+
/**
21+
* Perform HTTP GET request
22+
*
23+
* # Safety
24+
* - `url` must be a valid null-terminated C string
25+
* - Caller must call `client_response_free` on the returned value
26+
*/
27+
struct ClientResponse client_get(const char *url);
28+
29+
/**
30+
* Perform HTTP POST request
31+
*
32+
* # Safety
33+
* - `url` and `payload` must be valid null-terminated C strings
34+
* - Caller must call `client_response_free` on the returned value
35+
*/
36+
struct ClientResponse client_post(const char *url, const char *payload);
37+
38+
/**
39+
* Register a user and obtain a credential
40+
*
41+
* # Safety
42+
* - All parameters must be valid null-terminated C strings
43+
* - Caller must call `client_response_free` on the returned value
44+
*/
45+
struct ClientResponse userauth_register(const char *url,
46+
const char *public_params,
47+
const char *manifest_version);
48+
49+
/**
50+
* Submit user credentials with measurement data
51+
*
52+
* # Safety
53+
* - All parameters must be valid null-terminated C strings
54+
* - `credential_b64` must be a valid base64-encoded credential
55+
* - `public_params` must be valid base64 public parameters
56+
* - Caller must call `client_response_free` on the returned value
57+
*/
58+
struct ClientResponse userauth_submit(const char *url,
59+
const char *credential_b64,
60+
const char *public_params,
61+
const char *probe_cc,
62+
const char *probe_asn,
63+
const char *manifest_version);

internal/userauth/userauth.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
//go:build ooni_userauth
2+
3+
package userauth
4+
5+
// #cgo CFLAGS: -I${SRCDIR}/ffi
6+
// #cgo linux,amd64 LDFLAGS: -L${SRCDIR}/lib/linux/amd64 -looniprobe_userauth -ldl -lm -lpthread
7+
// #cgo linux,arm64 LDFLAGS: -L${SRCDIR}/lib/linux/arm64 -looniprobe_userauth -ldl -lm -lpthread
8+
// #cgo darwin,amd64 LDFLAGS: -L${SRCDIR}/lib/darwin/amd64 -looniprobe_userauth -framework CoreFoundation -framework Security
9+
// #cgo darwin,arm64 LDFLAGS: -L${SRCDIR}/lib/darwin/arm64 -looniprobe_userauth -framework CoreFoundation -framework Security
10+
// #cgo windows,amd64 LDFLAGS: -L${SRCDIR}/lib/windows/amd64 -looniprobe_userauth -lws2_32 -luserenv -lbcrypt
11+
// #include <stdlib.h>
12+
// #include "ooniprobe_userauth.h"
13+
import "C"
14+
15+
import (
16+
"encoding/json"
17+
"errors"
18+
"unsafe"
19+
)
20+
21+
// RegistrationResponse represents the result of user registration
22+
type RegistrationResponse struct {
23+
Credential string `json:"credential"`
24+
EmissionDay int16 `json:"emission_day"`
25+
}
26+
27+
// SubmitResponse represents the result of submitting measurement
28+
type SubmitResponse struct {
29+
MeasurementUID string `json:"measurement_uid,omitempty"`
30+
IsVerified bool `json:"is_verified"`
31+
SubmitResponse string `json:"submit_response"`
32+
}
33+
34+
// HTTPGet performs an HTTP GET request
35+
func HTTPGet(url string) (string, error) {
36+
cURL := C.CString(url)
37+
defer C.free(unsafe.Pointer(cURL))
38+
39+
resp := C.client_get(cURL)
40+
defer C.client_response_free(resp)
41+
42+
return parseResponse(resp)
43+
}
44+
45+
// HTTPPost performs an HTTP POST request
46+
func HTTPPost(url, payload string) (string, error) {
47+
cURL := C.CString(url)
48+
defer C.free(unsafe.Pointer(cURL))
49+
50+
cPayload := C.CString(payload)
51+
defer C.free(unsafe.Pointer(cPayload))
52+
53+
resp := C.client_post(cURL, cPayload)
54+
defer C.client_response_free(resp)
55+
56+
return parseResponse(resp)
57+
}
58+
59+
// Register registers a new user and obtains a credential
60+
func Register(url, publicParams, manifestVersion string) (*RegistrationResponse, error) {
61+
cURL := C.CString(url)
62+
defer C.free(unsafe.Pointer(cURL))
63+
64+
cPublicParams := C.CString(publicParams)
65+
defer C.free(unsafe.Pointer(cPublicParams))
66+
67+
cManifestVersion := C.CString(manifestVersion)
68+
defer C.free(unsafe.Pointer(cManifestVersion))
69+
70+
resp := C.userauth_register(cURL, cPublicParams, cManifestVersion)
71+
defer C.client_response_free(resp)
72+
73+
jsonStr, err := parseResponse(resp)
74+
if err != nil {
75+
return nil, err
76+
}
77+
78+
var result RegistrationResponse
79+
if err := json.Unmarshal([]byte(jsonStr), &result); err != nil {
80+
return nil, errors.New("failed to parse registration response: " + err.Error())
81+
}
82+
83+
return &result, nil
84+
}
85+
86+
// Submit submits user credentials with measurement data
87+
func Submit(url, credentialB64, publicParams, probeCC, probeASN, manifestVersion string) (*SubmitResponse, error) {
88+
cURL := C.CString(url)
89+
defer C.free(unsafe.Pointer(cURL))
90+
91+
cCredentialB64 := C.CString(credentialB64)
92+
defer C.free(unsafe.Pointer(cCredentialB64))
93+
94+
cPublicParams := C.CString(publicParams)
95+
defer C.free(unsafe.Pointer(cPublicParams))
96+
97+
cProbeCC := C.CString(probeCC)
98+
defer C.free(unsafe.Pointer(cProbeCC))
99+
100+
cProbeASN := C.CString(probeASN)
101+
defer C.free(unsafe.Pointer(cProbeASN))
102+
103+
cManifestVersion := C.CString(manifestVersion)
104+
defer C.free(unsafe.Pointer(cManifestVersion))
105+
106+
resp := C.userauth_submit(cURL, cCredentialB64, cPublicParams, cProbeCC, cProbeASN, cManifestVersion)
107+
defer C.client_response_free(resp)
108+
109+
jsonStr, err := parseResponse(resp)
110+
if err != nil {
111+
return nil, err
112+
}
113+
114+
var result SubmitResponse
115+
if err := json.Unmarshal([]byte(jsonStr), &result); err != nil {
116+
return nil, errors.New("failed to parse submit response: " + err.Error())
117+
}
118+
119+
return &result, nil
120+
}
121+
122+
// parseResponse converts a C ClientResponse to Go types
123+
func parseResponse(resp C.ClientResponse) (string, error) {
124+
if resp.error != nil {
125+
errStr := C.GoString(resp.error)
126+
return "", errors.New(errStr)
127+
}
128+
129+
if resp.json != nil {
130+
return C.GoString(resp.json), nil
131+
}
132+
133+
return "", errors.New("empty response from FFI")
134+
}

0 commit comments

Comments
 (0)