Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: retrieving multiple signing keys #27

Merged
merged 5 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions pg-cli/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use pg_core::api::*;
use pg_core::artifacts::{PublicKey, SigningKeyExt, UserSecretKey, VerifyingKey};
use pg_core::artifacts::{PublicKey, UserSecretKey, VerifyingKey};
use pg_core::kem::IBKEM;

use pg_core::kem::cgw_kv::CGWKV;
Expand Down Expand Up @@ -149,16 +149,17 @@ impl<'a> Client<'a> {
pub async fn request_signing_key(
&self,
auth: &str,
) -> Result<KeyResponse<SigningKeyExt>, ClientError> {
body: &SigningKeyRequest,
) -> Result<SigningKeyResponse, ClientError> {
let res = self
.client
.get(self.create_url("v2/irma/sign/key"))
.post(self.create_url("v2/irma/sign/key"))
.bearer_auth(auth)
.headers(HEADERS.clone())
.json(body)
.send()
.await?
.error_for_status()?
.json::<KeyResponse<SigningKeyExt>>()
.json::<SigningKeyResponse>()
.await?;

Ok(res)
Expand Down Expand Up @@ -187,16 +188,17 @@ impl<'a> Client<'a> {
Err(ClientError::Timeout)
}

pub async fn wait_on_signing_key(
pub async fn wait_on_signing_keys(
&self,
sp: &irma::SessionData,
) -> Result<KeyResponse<SigningKeyExt>, ClientError> {
body: &SigningKeyRequest,
) -> Result<SigningKeyResponse, ClientError> {
for _ in 0..120 {
let jwt: String = self.request_jwt(&sp.token).await?;
let kr = self.request_signing_key(&jwt).await?;
let kr = self.request_signing_key(&jwt, body).await?;

match kr {
kr @ KeyResponse::<SigningKeyExt> {
kr @ SigningKeyResponse {
status: irma::SessionStatus::Done,
..
} => return Ok(kr),
Expand Down
57 changes: 27 additions & 30 deletions pg-cli/src/encrypt.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use pg_core::api::IrmaAuthRequest;
use pg_core::api::{IrmaAuthRequest, SigningKeyRequest, SigningKeyResponse};
use pg_core::client::rust::stream::SealerStreamConfig;
use pg_core::client::Sealer;
use pg_core::identity::{Attribute, Policy};
Expand All @@ -25,7 +25,7 @@ pub async fn exec(enc_opts: EncOpts) {
let EncOpts {
input,
identity,
pub_sign_id,
pub_sign_id: pub_sign_id_str,
priv_sign_id,
pkg,
} = enc_opts;
Expand All @@ -47,9 +47,18 @@ pub async fn exec(enc_opts: EncOpts) {
})
.collect();

let pub_sig_id: Vec<Attribute> = serde_json::from_str(&pub_sign_id).unwrap();
let client = crate::client::Client::new(&pkg).unwrap();
let pub_sign_id: Vec<Attribute> = serde_json::from_str(&pub_sign_id_str).unwrap();
let mut total_id = pub_sign_id.clone();

let priv_sign_id = if let Some(priv_sign_id_str) = priv_sign_id {
let priv_id: Vec<Attribute> = serde_json::from_str(&priv_sign_id_str).unwrap();
total_id.extend(priv_id.clone());
Some(priv_id)
} else {
None
};

let client = crate::client::Client::new(&pkg).unwrap();
let parameters = client.parameters().await.unwrap();

eprintln!("Fetched parameters from {}", pkg);
Expand All @@ -59,41 +68,29 @@ pub async fn exec(enc_opts: EncOpts) {
serde_json::to_string_pretty(&policies).unwrap()
);

eprintln!("retrieving signing keys...");
eprintln!("Retrieving signing keys...");

let sd = client
.request_start(&IrmaAuthRequest {
con: pub_sig_id,
con: total_id,
validity: None,
})
.await
.unwrap();

print_qr(&sd.session_ptr);

let pub_sign_key = client.wait_on_signing_key(&sd).await.unwrap().key.unwrap();

let priv_sign_key = match priv_sign_id {
Some(id) => {
let priv_sign_id: Vec<Attribute> = serde_json::from_str(&id).unwrap();

let sd = client
.request_start(&IrmaAuthRequest {
con: priv_sign_id,
validity: None,
})
.await
.unwrap();

print_qr(&sd.session_ptr);

let priv_sign_key = client.wait_on_signing_key(&sd).await.unwrap().key.unwrap();

Some(priv_sign_key)
}
None => None,
let skr = SigningKeyRequest {
pub_sign_id,
priv_sign_id,
};

let SigningKeyResponse {
pub_sign_key,
priv_sign_key,
..
} = client.wait_on_signing_keys(&sd, &skr).await.unwrap();

let input_path = Path::new(&input);
let file_name_path = input_path.file_name().unwrap();
let file_name = file_name_path.to_str().unwrap();
Expand All @@ -117,13 +114,13 @@ pub async fn exec(enc_opts: EncOpts) {
let mut sealer = Sealer::<_, SealerStreamConfig>::new(
&parameters.public_key,
&policies,
&pub_sign_key,
&pub_sign_key.expect("no public signing key"),
&mut rng,
)
.unwrap();

if let Some(key) = priv_sign_key {
sealer = sealer.with_priv_signing_key(key);
if let Some(psk) = priv_sign_key {
sealer = sealer.with_priv_signing_key(psk);
};

sealer.seal(r, w).await.unwrap();
Expand Down
35 changes: 34 additions & 1 deletion pg-core/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Definitions of the PostGuard protocol REST API.

use crate::identity::Attribute;
use crate::{artifacts::SigningKeyExt, identity::Attribute};
use alloc::vec::Vec;
use irma::{ProofStatus, SessionStatus};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -41,3 +41,36 @@ pub struct KeyResponse<T> {
#[serde(skip_serializing_if = "Option::is_none")]
pub key: Option<T>,
}

/// The request Signing key request body.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SigningKeyRequest {
/// The public signing identity.
pub pub_sign_id: Vec<Attribute>,

/// The private signing identity.
#[serde(skip_serializing_if = "Option::is_none")]
pub priv_sign_id: Option<Vec<Attribute>>,
}

/// The signing key response from the Private Key Generator (PKG).
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SigningKeyResponse {
/// The status of the session.
pub status: SessionStatus,

/// The status of the IRMA proof.
#[serde(skip_serializing_if = "Option::is_none")]
pub proof_status: Option<ProofStatus>,

/// The public signing key.
/// The key will remain `None` until the status is `Done` and the proof is `Valid`.
#[serde(skip_serializing_if = "Option::is_none")]
pub pub_sign_key: Option<SigningKeyExt>,

/// This private signing key.
#[serde(skip_serializing_if = "Option::is_none")]
pub priv_sign_key: Option<SigningKeyExt>,
}
39 changes: 34 additions & 5 deletions pg-pkg/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,39 @@ The `status` field will always be included. The `proofStatus` and `key` values
are optional and depend on the JWT. A key is included if and only if the proof
was valid and all the claimed attributes were present. A key is derived from these attributes.

### `GET /v2/irma/sign/key`
### `POST /v2/irma/sign/key`

Retrieves a signing key. The request must include a HTTP Authorization header
`Authorization: Bearer <JWT>`.
Retrieves signing key(s). The request must include a HTTP Authorization header
`Authorization: Bearer <JWT>`. The body must include under which identities a user wants to sign.

The response looks similar as `GET /v2/irma/key/{timestamp}`, except the key is
a signing key.
```JSON
{
"pubSignId": [
{ "t": "irma-demo.gemeente.personalData.fullname", "v": "Alice" }
],
"privSignId": [{ "t": "irma-demo.gemeente.personalData.bsn", "v": "1234" }]
}
```

The response looks similar as `GET /v2/irma/key/{timestamp}`, except with signing keys.

```JSON
{
"status": "DONE",
"proofStatus": "VALID",
"pubSignKey": {
"key": "/VBSvTSsTloj5xUKH1EDWN1s6c9Z5L1UqL2NGJnpaQMoFa2sjLw+cjA8P5OD3AwP7zv1VcU7Tzon/8J/vnVLbzGNswBZk5KAjYZVrFNZx34/5Hbk28ajjqVA4fKqNawB",
"policy": {
"ts": 1695723474,
"con": [{ "t": "irma-demo.gemeente.personalData.fullname", "v": "Alice" }]
}
},
"privSignKey": {
"key": "Uk+BFli0n5yz8huZCQWiztgdo3KvN9Y6XcsPc+IAmARKXGUApvaYYTCi+7WdjxZzXs1mnrAas3r5wuWu2ecuQaSyboyIuCbGD/P7+FO1rc712czlVm6RxKrZx4BjlsqU",
"policy": {
"ts": 1695723474,
"con": [{ "t": "irma-demo.gemeente.personalData.bsn", "v": "1234" }]
}
}
}
```
64 changes: 51 additions & 13 deletions pg-pkg/src/handlers/signing_key.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
use actix_web::{web::Data, HttpResponse};
use actix_web::{web::Data, web::Json, HttpResponse};
use actix_web::{HttpMessage, HttpRequest};

use pg_core::api::KeyResponse;
use irma::SessionStatus;
use pg_core::api::{SigningKeyRequest, SigningKeyResponse};
use pg_core::artifacts::{SigningKey, SigningKeyExt};
use pg_core::ibs::gg::{keygen, SecretKey};
use pg_core::identity::Policy;

use pg_core::ibs::gg::{keygen, Identity, SecretKey};

use crate::middleware::irma::IrmaAuthResult;
use crate::util::current_time_u64;

pub async fn signing_key(
req: HttpRequest,
msk: Data<SecretKey>,
body: Json<SigningKeyRequest>,
) -> Result<HttpResponse, crate::Error> {
let sk = msk.get_ref();
let mut rng = rand::thread_rng();
Expand All @@ -32,23 +33,60 @@ pub async fn signing_key(

// The PKG gets to decide the timestamp in the policy.
let iat = current_time_u64()?;
let body = body.into_inner();

match status {
SessionStatus::Done => (),
_ => {
return Ok(HttpResponse::Ok().json(SigningKeyResponse {
status,
proof_status,
pub_sign_key: None,
priv_sign_key: None,
}))
}
}

if !body.pub_sign_id.iter().all(|attr| con.contains(attr)) {
return Err(crate::Error::Unexpected);
}

let policy = Policy {
timestamp: iat,
con,
con: body.pub_sign_id.clone(),
};
let id = policy.derive_ibs().map_err(|_e| crate::Error::Unexpected)?;
let key = keygen(sk, &id, &mut rng);

let derived = policy.derive_ibs().map_err(|_e| crate::Error::Unexpected)?;
let pub_sign_key = SigningKeyExt {
key: SigningKey(key),
policy,
};

let id = Identity::from(derived);
let key = keygen(sk, &id, &mut rng);
let priv_sign_key = body.priv_sign_id.map(|priv_sign_id| {
if !priv_sign_id.iter().all(|attr| con.contains(attr)) {
return Err(crate::Error::Unexpected);
}
let policy = Policy {
timestamp: iat,
con: priv_sign_id,
};

Ok(HttpResponse::Ok().json(KeyResponse::<SigningKeyExt> {
status,
proof_status,
key: Some(SigningKeyExt {
let id = policy.derive_ibs().map_err(|_e| crate::Error::Unexpected)?;
let key = keygen(sk, &id, &mut rng);

Ok(SigningKeyExt {
key: SigningKey(key),
policy,
}),
})
});

let priv_sign_key = priv_sign_key.map_or(Ok(None), |r| r.map(Some))?;

Ok(HttpResponse::Ok().json(SigningKeyResponse {
status,
proof_status,
pub_sign_key: Some(pub_sign_key),
priv_sign_key,
}))
}
Loading
Loading