Skip to content

Commit

Permalink
Add support for uploading signatures to rekor (project-oak#1914)
Browse files Browse the repository at this point in the history
Change the public key format to be more in line with standard x509 /
PKIX, which rekor supports.

Add basic (and hacky) instructions on how to publish signatures to
rekor using their CLI.

Sample created entry:
https://api.rekor.dev/api/v1/log/entries/2f2f56599f8ae02409224c28143b72a8ff24db514011d493dbf91379b0ea7ca3

Ref project-oak#1912
  • Loading branch information
tiziano88 committed Mar 24, 2021
1 parent 0b4a477 commit 57d9652
Show file tree
Hide file tree
Showing 38 changed files with 373 additions and 63 deletions.
2 changes: 2 additions & 0 deletions examples/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions examples/abitest/client/cpp/httptest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,17 @@ const char* CA_CERT_PATH = "../../../../../../../../../examples/certs/local/ca.p
const int PORT = 8383;

// Generated using the command:
//
// ```shell
// cargo run --manifest-path=oak_sign/Cargo.toml -- \
// generate \
// --private-key=http-test.key \
// --public-key=http-test.pub
// ```
const char* PUBLIC_KEY_BASE64 = "yTOK5pP6S1ebFJeOhB8KUxBY293YbBo/TW5h1/1UdKM=";
const char* PUBLIC_KEY_BASE64 = "MCowBQYDK2VwAyEAmSeVNkzZtyQ3v4ibhsdetuOW7j+pIQDK4UJxSEfngMM=";

// Generated using the command:
//
// ```shell
// cargo run --manifest-path=oak_sign/Cargo.toml -- \
// sign \
Expand All @@ -43,7 +45,7 @@ const char* PUBLIC_KEY_BASE64 = "yTOK5pP6S1ebFJeOhB8KUxBY293YbBo/TW5h1/1UdKM=";
// --signature-file=http-test.sign
// ```
const char* SIGNED_HASH_BASE64 =
"rpFVU/NAIDE62/hpE0DMobLsAJ+tDLNATgPLaX8PbN6v0XeACdCNspL0YY1QfyvJN2mq3Z2h4JWgS/lVkMcHAg==";
"0OSnT9/FwnkuLN33yPQIPQaYbQJZGNxxFv3H/5lNdBy5d+IqS72C0MImora1QVAlrQ/AxldIfsQIJzW+LMT1CQ==";

// Simple manual test case registry.
const std::map<std::string, HttpTestFn> http_tests = {
Expand Down
4 changes: 4 additions & 0 deletions examples/aggregator/client/cpp/aggregator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ int main(int argc, char** argv) {
//
// The particular value corresponds to the hash on the `aggregator.wasm` line in
// https://github.com/project-oak/oak/blob/hashes/reproducibility_index.
//
// In order to re-compute this locally, run `/scripts/build_reproducibility_index`, and copy the
// appropriate value here.
//
// TODO(#1674): Add appropriate TLS endpoint tag to the label as well.
oak::label::Label label = oak::WebAssemblyModuleHashLabel(
absl::HexStringToBytes("4b652112bb9904976fd5a215a4df081956165f1dbc19bf6c77e0eef05c4bc256"));
Expand Down
2 changes: 1 addition & 1 deletion examples/chat/module/rust/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ impl<'a> Chatter<'a> {
info!(
"creating new Chatter({}, {})",
user_handle,
base64::encode(&room_key_pair.pkcs8_public_key())
base64::encode(&room_key_pair.public_key_der().unwrap())
);

// Use key pair to label requests and authenticate this client.
Expand Down
2 changes: 1 addition & 1 deletion examples/hello_world/client/nodejs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ async function main() {
);
const signedChallenge = oakSignedChallengeType.create({
signedHash: signedHash,
publicKey: keyPair.publicKey,
publicKey: keyPair.publicKeyDer,
});
const encodedSignedChallenge = oakSignedChallengeType
.encode(signedChallenge)
Expand Down
51 changes: 48 additions & 3 deletions examples/hello_world/client/nodejs/oakSign.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,54 @@ const { createHash } = require('crypto');
/**
* @typedef {Object} KeyPair
* @property {Uint8Array} privateKey
* @property {Uint8Array} publicKey
* @property {Uint8Array} publicKeyDer
*/

/**
* @return {KeyPair}
*/
async function generateKeyPair() {
const privateKey = ed25519.utils.randomPrivateKey();
const publicKey = await ed25519.getPublicKey(privateKey);
const publicKeyRaw = await ed25519.getPublicKey(privateKey);

// Since JS does not have functions to convert to / from ASN.1, we fake this by manually
// pre-computing and hardcoding a fixed prefix that, when concatenated with the actual raw key
// bytes, produces a valid ASN.1-encoded Ed25519 public key.
//
// This is the meaning of the prefix bytes interpreted as ASN.1:
//
// 30 2a : SEQUENCE len 42 (SubjectPublicKeyInfo)
// 30 05 : SEQUENCE len 5 (AlgorithmIdentifier)
// 06 03 : OBJECT IDENTIFIER len 3
// 2b6570 : 1.3.101.112 = Ed25519
// 03 21 : BIT STRING len 33
// 00 : number of padding bits at end of content
// 7f8d520a536d4788b8eafd93ba1d5f40b6edfd9a91af594435a8c25bdda3c8fe : [raw public key]
//
// Also see:
//
// - /oak/common/oak_sign.h
// - https://github.com/project-oak/oak/issues/1912#issuecomment-802689201
// - https://tools.ietf.org/html/rfc5280#section-4.1 (SubjectPublicKeyInfo)
const publicKeyDerPrefix = new Uint8Array([
0x30,
0x2a,
0x30,
0x05,
0x06,
0x03,
0x2b,
0x65,
0x70,
0x03,
0x21,
0x00,
]);
const publicKeyDer = concatenateArrays(publicKeyDerPrefix, publicKeyRaw);

return {
privateKey,
publicKey,
publicKeyDer,
};
}

Expand All @@ -48,6 +83,16 @@ async function hashAndSign(input, privateKey) {
return signedHash;
}

/**
* @param {Uint8Array} a
* @param {Uint8Array} b
* @return {Uint8Array}
*/
function concatenateArrays(a, b) {
// See https://stackoverflow.com/a/60590943/269518.
return new Uint8Array([...a, ...b]);
}

module.exports = {
generateKeyPair,
hashAndSign,
Expand Down
2 changes: 1 addition & 1 deletion examples/http_server/client/rust/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ async fn main() -> Result<()> {
// Signed challenge
let signature = oak_abi::proto::oak::identity::SignedChallenge {
signed_hash: signature.signed_hash,
public_key: key_pair.pkcs8_public_key(),
public_key: key_pair.public_key_der()?,
};

let path = &opt.ca_cert;
Expand Down
4 changes: 2 additions & 2 deletions examples/keys/ed25519/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ In order to create a new pair of public/private keys, run the following command:

```bash
./scripts/oak_sign generate \
--private-key=examples/certs/ed25519/test.key \
--public-key=examples/certs/ed25519/test.pub
--private-key=./examples/keys/ed25519/test.key \
--public-key=./examples/keys/ed25519/test.pub
```
2 changes: 1 addition & 1 deletion examples/keys/ed25519/test.pub
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
-----BEGIN PUBLIC KEY-----
f41SClNtR4i46v2Tuh1fQLbt/ZqRr1lENajCW92jyP4=
MCowBQYDK2VwAyEAf41SClNtR4i46v2Tuh1fQLbt/ZqRr1lENajCW92jyP4=
-----END PUBLIC KEY-----
38 changes: 35 additions & 3 deletions examples/private_set_intersection/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,39 @@ the code is modified, the wasm module and the signature must be regenerated:

```bash
./scripts/oak_sign sign \
--private-key=examples/keys/ed25519/test.key \
--input-file=examples/private_set_intersection/bin/private_set_intersection_handler.wasm \
--signature-file=examples/private_set_intersection/private_set_intersection_handler.sign
--private-key=./examples/keys/ed25519/test.key \
--input-file=./examples/private_set_intersection/bin/private_set_intersection_handler.wasm \
--signature-file=./examples/private_set_intersection/private_set_intersection_handler.sign
```

## To publish the signature to rekor

rekor is a public verifiable log of signatures.

See <https://sigstore.dev/what_is_sigstore/> for details about rekor.

1. build the `rekor` CLI: <https://sigstore.dev/get_started/client/>
1. ```bash
rekor upload \
--artifact=<(base64 --decode <(sed -n '11,11p' ./examples/private_set_intersection/private_set_intersection_handler.sign | tr -d '[:space:]')) \
--signature=<(base64 --decode <(sed -n '6,7p' ./examples/private_set_intersection/private_set_intersection_handler.sign | tr -d '[:space:]')) \
--public-key=./examples/keys/ed25519/test.pub \
--pki-format=x509
```

## To look up signatures from rekor

- by key:

```bash
rekor search \
--public-key=./examples/keys/ed25519/test.pub \
--pki-format=x509
```

- by hash:

```bash
rekor search \
--artifact=<(base64 --decode <(sed -n '11,11p' ./examples/private_set_intersection/private_set_intersection_handler.sign | tr -d '[:space:]'))
```
4 changes: 2 additions & 2 deletions examples/private_set_intersection/main_module/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ use oak_services::proto::oak::log::LogInit;
use private_set_intersection_handler::Handler;

// Base64 encoded Ed25519 public key corresponding to Wasm module signature.
// Originated from `examples/keys/ed25519/test.pub`.
const PUBLIC_KEY_BASE64: &str = "f41SClNtR4i46v2Tuh1fQLbt/ZqRr1lENajCW92jyP4=";
// Originated from `/examples/keys/ed25519/test.pub`.
const PUBLIC_KEY_BASE64: &str = "MCowBQYDK2VwAyEAf41SClNtR4i46v2Tuh1fQLbt/ZqRr1lENajCW92jyP4=";

oak::entrypoint_command_handler!(oak_main => Main);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ async fn test_set_intersection() {
oak_runtime::configure_and_run(config).expect("Unable to configure runtime with test wasm");

let public_key_label =
confidentiality_label(web_assembly_module_signature_tag(&signature.public_key));
confidentiality_label(web_assembly_module_signature_tag(&signature.public_key_der));
let (channel, interceptor) = oak_tests::channel_and_interceptor(&public_key_label).await;
let mut client = PrivateSetIntersectionClient::with_interceptor(channel, interceptor);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-----BEGIN PUBLIC KEY-----
f41SClNtR4i46v2Tuh1fQLbt/ZqRr1lENajCW92jyP4=
MCowBQYDK2VwAyEAf41SClNtR4i46v2Tuh1fQLbt/ZqRr1lENajCW92jyP4=
-----END PUBLIC KEY-----

-----BEGIN SIGNATURE-----
Expand Down
2 changes: 1 addition & 1 deletion examples/trusted_database/client/rust/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ async fn main() -> anyhow::Result<()> {
.context("Couldn't create TLS channel")?;

let key_pair = oak_sign::KeyPair::generate()?;
let label = confidentiality_label(public_key_identity_tag(&key_pair.pkcs8_public_key()));
let label = confidentiality_label(public_key_identity_tag(&key_pair.public_key_der()?));
let label_interceptor =
LabelInterceptor::create(&label).context("Couldn't create gRPC interceptor")?;
let auth_interceptor = AuthInterceptor::create(key_pair);
Expand Down
2 changes: 2 additions & 0 deletions experimental/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion oak/client/application_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ class ApplicationClient {
std::string challenge) {
oak::Signature signature = key_pair->Sign(challenge);
oak::identity::SignedChallenge signed_challenge;
signed_challenge.set_public_key(signature.public_key);
signed_challenge.set_public_key(signature.public_key_der);
signed_challenge.set_signed_hash(signature.signed_hash);
return signed_challenge;
}
Expand Down
4 changes: 2 additions & 2 deletions oak/common/oak_sign.cc
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ std::unique_ptr<KeyPair> KeyPair::FromPkcs8(const std::string& pkcs8_private_key
}

std::string KeyPair::ToPkcs8() {
oak::pkcs8::PrivateKeyInfo pk_info{GetPrivateKey(), GetPublicKey()};
oak::pkcs8::PrivateKeyInfo pk_info{GetPrivateKey(), GetPublicKeyRaw()};
std::string pkcs8_encoded = oak::pkcs8::to_pkcs8(pk_info, oak::pkcs8::kEd25519Pkcs8Template);
return pkcs8_encoded;
}
Expand All @@ -89,7 +89,7 @@ Signature KeyPair::Sign(const std::string& message) {

Signature signature;
signature.signed_hash = signed_hash;
signature.public_key = key_.public_key().key_value();
signature.public_key_der = this->GetPublicKeyDer();

return signature;
}
Expand Down
30 changes: 28 additions & 2 deletions oak/common/oak_sign.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ ABSL_CONST_INIT extern const char kPrivateKeyPemTag[];

struct Signature {
std::string signed_hash;
std::string public_key;
std::string public_key_der;
};

class KeyPair {
Expand All @@ -39,7 +39,33 @@ class KeyPair {
// Generates a PKCS#8 encoded string from this key pair.
std::string ToPkcs8();

std::string GetPublicKey() { return key_.public_key().key_value(); }
// Returns the public key, as a raw Ed25519 key.
std::string GetPublicKeyRaw() { return key_.public_key().key_value(); }

// Returns the public key, as a binary DER-encoded SubjectPublicKeyInfo (see
// https://tools.ietf.org/html/rfc5280#section-4.1).
std::string GetPublicKeyDer() {
// Since Tink does not have functions to convert to / from ASN.1, we fake this by manually
// pre-computing and hardcoding a fixed prefix that, when concatenated with the actual raw key
// bytes, produces a valid ASN.1-encoded Ed25519 public key.
//
// This is the meaning of the prefix bytes interpreted as ASN.1:
//
// 30 2a : SEQUENCE len 42 (SubjectPublicKeyInfo)
// 30 05 : SEQUENCE len 5 (AlgorithmIdentifier)
// 06 03 : OBJECT IDENTIFIER len 3
// 2b6570 : 1.3.101.112 = Ed25519
// 03 21 : BIT STRING len 33
// 00 : number of padding bits at end of content
// 7f8d520a536d4788b8eafd93ba1d5f40b6edfd9a91af594435a8c25bdda3c8fe : [raw public key]
//
// Also see:
//
// - https://github.com/project-oak/oak/issues/1912#issuecomment-802689201
// - https://tools.ietf.org/html/rfc5280#section-4.1 (SubjectPublicKeyInfo)
return std::string({0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00}) +
key_.public_key().key_value();
}
std::string GetPrivateKey() { return key_.key_value(); }

// Generates a new KeyPair instance containing an ed25519 key pair.
Expand Down
1 change: 1 addition & 0 deletions oak/common/pkcs8.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ namespace pkcs8 {
// also omitted. That information is encapsulated in struct `Template`.
struct PrivateKeyInfo {
std::string private_key;
// Raw public key bytes.
std::string public_key;
};

Expand Down
3 changes: 3 additions & 0 deletions oak_abi/proto/identity.proto
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,8 @@ package oak.identity;
// public key corresponding to the private key used for signing.
message SignedChallenge {
bytes signed_hash = 1;

// Public key as a binary DER-encoded `SubjectPublicKeyInfo` (see
// https://tools.ietf.org/html/rfc5280#section-4.1).
bytes public_key = 2;
}
9 changes: 7 additions & 2 deletions oak_abi/proto/label.proto
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,13 @@ message WebAssemblyModuleTag {
// Policies related to modules, referring to the signature of the native WebAssembly node.
message WebAssemblyModuleSignatureTag {
// Public key counterpart of the private key used to sign WebAssembly module.
// In the current implementation is represented as a serialized Ed25519 public key.
// https://ed25519.cr.yp.to
//
// Ed25519 public key as a binary DER-encoded `SubjectPublicKeyInfo`.
//
// See:
//
// - https://tools.ietf.org/html/rfc5280#section-4.1
// - https://ed25519.cr.yp.to
bytes public_key = 1;
}

Expand Down
14 changes: 14 additions & 0 deletions oak_client/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 57d9652

Please sign in to comment.