blob: 53cd168fe0a518eee23ca7d04fd83ff50a44d227 [file] [log] [blame]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
//! BoringSSL-based implementation for the AuthGraph `Ec` trait.
use authgraph_core::{
ag_err, ag_verr,
error::Error,
key::{
check_cose_key_params, EcExchangeKey, EcExchangeKeyPriv, EcExchangeKeyPub, EcSignKey,
EcVerifyKey, EcdhSecret,
},
traits, try_to_vec,
};
use authgraph_wire::ErrorCode;
use core::ops::DerefMut;
use coset::{cbor::Value, iana, CoseKey, Label};
/// Initial byte of SEC1 public key encoding that indicates an uncompressed point.
pub const SEC1_UNCOMPRESSED_PREFIX: u8 = 0x04;
/// The struct implementing the Authgraph [`EcDh`] trait.
pub struct BoringEcDh;
impl traits::EcDh for BoringEcDh {
fn generate_key(&self) -> Result<EcExchangeKey, Error> {
let (priv_key, pub_key) = create_p256_key_pair(iana::Algorithm::ECDH_ES_HKDF_256)?;
Ok(EcExchangeKey {
priv_key: EcExchangeKeyPriv(priv_key),
pub_key: EcExchangeKeyPub(pub_key),
})
}
fn compute_shared_secret(
&self,
own_key: &EcExchangeKeyPriv,
peer_key: &EcExchangeKeyPub,
) -> Result<EcdhSecret, Error> {
let peer_key = p256_ecdh_pkey_from_cose(&peer_key.0)?;
let group =
ossl!(openssl::ec::EcGroup::from_curve_name(openssl::nid::Nid::X9_62_PRIME256V1))?;
// This method is an Android modification to the rust-openssl crate.
let ec_key = ossl!(openssl::ec::EcKey::private_key_from_der_for_group(&own_key.0, &group))?;
let pkey = ossl!(openssl::pkey::PKey::from_ec_key(ec_key))?;
let mut deriver = ossl!(openssl::derive::Deriver::new(&pkey))?;
ossl!(deriver.set_peer(&peer_key))
.map_err(|e| ag_err!(InvalidPeerKeKey, "peer key invalid: {:?}", e))?;
let derived = ossl!(deriver.derive_to_vec())?;
Ok(EcdhSecret(derived))
}
}
/// The struct implementing the Authgraph [`EcDsa`] trait.
pub struct BoringEcDsa;
impl traits::EcDsa for BoringEcDsa {
fn sign(&self, sign_key: &EcSignKey, data: &[u8]) -> Result<Vec<u8>, Error> {
let pkey;
let mut signer = match sign_key {
EcSignKey::Ed25519(key) => {
pkey = ossl!(openssl::pkey::PKey::private_key_from_raw_bytes(
key,
openssl::pkey::Id::ED25519
))?;
// Ed25519 has an internal digest so needs no external digest.
ossl!(openssl::sign::Signer::new_without_digest(&pkey))
}
EcSignKey::P256(key) => {
let group = ossl!(openssl::ec::EcGroup::from_curve_name(
openssl::nid::Nid::X9_62_PRIME256V1
))?;
let digest = openssl::hash::MessageDigest::sha256();
let ec_key =
ossl!(openssl::ec::EcKey::private_key_from_der_for_group(key, &group))?;
pkey = ossl!(openssl::pkey::PKey::from_ec_key(ec_key))?;
ossl!(openssl::sign::Signer::new(digest, &pkey))
}
EcSignKey::P384(key) => {
let group =
ossl!(openssl::ec::EcGroup::from_curve_name(openssl::nid::Nid::SECP384R1))?;
// Note that the CDDL for PubKeyECDSA384 specifies SHA-384 as the digest.
let digest = openssl::hash::MessageDigest::sha384();
let ec_key =
ossl!(openssl::ec::EcKey::private_key_from_der_for_group(key, &group))?;
pkey = ossl!(openssl::pkey::PKey::from_ec_key(ec_key))?;
ossl!(openssl::sign::Signer::new(digest, &pkey))
}
}?;
ossl!(signer.sign_oneshot_to_vec(data))
}
fn verify_signature(
&self,
verify_key: &EcVerifyKey,
data: &[u8],
signature: &[u8],
) -> Result<(), Error> {
let pkey;
let mut verifier = match verify_key {
EcVerifyKey::Ed25519(key) => {
pkey = ed25519_ecdsa_pkey_from_cose(key)?;
ossl!(openssl::sign::Verifier::new_without_digest(&pkey))
}
EcVerifyKey::P256(key) => {
pkey = p256_ecdsa_pkey_from_cose(key)?;
let digest = openssl::hash::MessageDigest::sha256();
ossl!(openssl::sign::Verifier::new(digest, &pkey))
}
EcVerifyKey::P384(key) => {
pkey = p384_ecdsa_pkey_from_cose(key)?;
let digest = openssl::hash::MessageDigest::sha384();
ossl!(openssl::sign::Verifier::new(digest, &pkey))
}
}?;
if ossl!(verifier.verify_oneshot(signature, data))? {
Ok(())
} else {
Err(ag_err!(InvalidSignature, "signature verification failed"))
}
}
}
/// Create an EC key pair on P-256 curve and return a tuple consisting of the private key bytes
/// and the public key as a `CoseKey`.
pub fn create_p256_key_pair(algorithm: iana::Algorithm) -> Result<(Vec<u8>, CoseKey), Error> {
let group = ossl!(openssl::ec::EcGroup::from_curve_name(openssl::nid::Nid::X9_62_PRIME256V1))?;
let ec_priv_key = ossl!(openssl::ec::EcKey::<openssl::pkey::Private>::generate(&group))?;
// Convert the private key to DER-encoded `ECPrivateKey` as per RFC5915 s3, which is
// our choice of private key encoding.
let priv_key = ossl!(ec_priv_key.private_key_to_der())?;
// Get the public key point, and convert to (x, y) coordinates.
let pub_key_pt = ec_priv_key.public_key();
let mut bn_ctx = ossl!(openssl::bn::BigNumContext::new())?;
let pub_key_sec1 = ossl!(pub_key_pt.to_bytes(
group.as_ref(),
openssl::ec::PointConversionForm::UNCOMPRESSED,
bn_ctx.deref_mut()
))?;
let (x, y) = crate::ec::coords_from_p256_pub_key(&pub_key_sec1)?;
let pub_key = coset::CoseKeyBuilder::new_ec2_pub_key(iana::EllipticCurve::P_256, x, y)
.algorithm(algorithm)
.build();
Ok((priv_key, pub_key))
}
/// Helper function to return the (x,y) coordinates, given the public key as a SEC-1 encoded
/// uncompressed point. 0x04: uncompressed, followed by x || y coordinates.
pub fn coords_from_p256_pub_key(pub_key: &[u8]) -> Result<(Vec<u8>, Vec<u8>), Error> {
let coord_len = 32; // For P-256
if pub_key.len() != (1 + 2 * coord_len) {
return Err(ag_err!(
InternalError,
"unexpected SEC1 pubkey len of {} for P-256",
pub_key.len(),
));
}
if pub_key[0] != SEC1_UNCOMPRESSED_PREFIX {
return Err(ag_err!(
InternalError,
"unexpected SEC1 pubkey initial byte {} for P-256",
pub_key[0],
));
}
Ok((try_to_vec(&pub_key[1..1 + coord_len])?, try_to_vec(&pub_key[1 + coord_len..])?))
}
/// Convert an ECDH P-256 `COSE_Key` into an [`openssl::pkey::PKey`].
fn p256_ecdh_pkey_from_cose(
cose_key: &coset::CoseKey,
) -> Result<openssl::pkey::PKey<openssl::pkey::Public>, Error> {
nist_pkey_from_cose(
cose_key,
iana::KeyType::EC2,
iana::Algorithm::ECDH_ES_HKDF_256, // ECDH
iana::EllipticCurve::P_256,
openssl::nid::Nid::X9_62_PRIME256V1,
ErrorCode::InvalidPeerKeKey,
)
}
/// Convert an ECDSA P-256 `COSE_Key` into an [`openssl::pkey::PKey`].
fn p256_ecdsa_pkey_from_cose(
cose_key: &coset::CoseKey,
) -> Result<openssl::pkey::PKey<openssl::pkey::Public>, Error> {
nist_pkey_from_cose(
cose_key,
iana::KeyType::EC2,
iana::Algorithm::ES256, // ECDSA
iana::EllipticCurve::P_256,
openssl::nid::Nid::X9_62_PRIME256V1,
ErrorCode::InvalidCertChain,
)
}
/// Convert an ECDSA P-384 `COSE_Key` into an [`openssl::pkey::PKey`].
fn p384_ecdsa_pkey_from_cose(
cose_key: &coset::CoseKey,
) -> Result<openssl::pkey::PKey<openssl::pkey::Public>, Error> {
nist_pkey_from_cose(
cose_key,
iana::KeyType::EC2,
iana::Algorithm::ES384,
iana::EllipticCurve::P_384,
openssl::nid::Nid::SECP384R1,
ErrorCode::InvalidCertChain,
)
}
fn nist_pkey_from_cose(
cose_key: &coset::CoseKey,
want_kty: iana::KeyType,
want_alg: iana::Algorithm,
want_curve: iana::EllipticCurve,
nid: openssl::nid::Nid,
err_code: ErrorCode,
) -> Result<openssl::pkey::PKey<openssl::pkey::Public>, Error> {
// First check that the COSE_Key looks sensible.
check_cose_key_params(cose_key, want_kty, want_alg, want_curve, err_code)?;
// Extract (x, y) coordinates (big-endian).
let x = cose_key
.params
.iter()
.find_map(|(l, v)| match (l, v) {
(Label::Int(l), Value::Bytes(data)) if *l == iana::Ec2KeyParameter::X as i64 => {
Some(data)
}
_ => None,
})
.ok_or_else(|| ag_verr!(err_code, "no x coord"))?;
let y = cose_key
.params
.iter()
.find_map(|(l, v)| match (l, v) {
(Label::Int(l), Value::Bytes(data)) if *l == iana::Ec2KeyParameter::Y as i64 => {
Some(data)
}
_ => None,
})
.ok_or_else(|| ag_verr!(err_code, "no y coord"))?;
let x = ossl!(openssl::bn::BigNum::from_slice(x))?;
let y = ossl!(openssl::bn::BigNum::from_slice(y))?;
let group = ossl!(openssl::ec::EcGroup::from_curve_name(nid))?;
let ec_key = ossl!(openssl::ec::EcKey::from_public_key_affine_coordinates(&group, &x, &y))?;
let pkey = ossl!(openssl::pkey::PKey::from_ec_key(ec_key))?;
Ok(pkey)
}
/// Convert an EdDSA Ed25519 `COSE_Key` into an [`openssl::pkey::PKey`].
fn ed25519_ecdsa_pkey_from_cose(
cose_key: &coset::CoseKey,
) -> Result<openssl::pkey::PKey<openssl::pkey::Public>, Error> {
// First check that the COSE_Key looks sensible.
check_cose_key_params(
cose_key,
iana::KeyType::OKP,
iana::Algorithm::EdDSA,
iana::EllipticCurve::Ed25519,
ErrorCode::InvalidCertChain,
)?;
// Extract x coordinate (little-endian).
let x = cose_key
.params
.iter()
.find_map(|(l, v)| match (l, v) {
(Label::Int(l), Value::Bytes(data)) if *l == iana::OkpKeyParameter::X as i64 => {
Some(data)
}
_ => None,
})
.ok_or_else(|| ag_err!(InvalidCertChain, "no x coord"))?;
let pkey =
ossl!(openssl::pkey::PKey::public_key_from_raw_bytes(x, openssl::pkey::Id::ED25519))?;
Ok(pkey)
}