1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 //! Secretkeeper client acts as the "source" `AuthGraphParticipant` in context of AuthGraph 18 //! Key Exchange, while Secretkeeper itself acts as "sink" `AuthGraphParticipant`. This module 19 //! supports functionality to configure the local "source" `AuthGraphParticipant` for clients. 20 21 extern crate alloc; 22 23 use authgraph_boringssl::{BoringAes, BoringRng}; 24 use authgraph_core::ag_err; 25 use authgraph_core::error::Error as AgError; 26 use authgraph_core::error::Error; 27 use authgraph_core::key::{ 28 AesKey, CertChain, EcSignKey, EcVerifyKey, Identity, IdentityVerificationDecision, 29 CURVE25519_PRIV_KEY_LEN, IDENTITY_VERSION, 30 }; 31 use authgraph_core::traits; 32 use authgraph_core::traits::AG_KEY_EXCHANGE_PROTOCOL_VERSION_1; 33 use authgraph_core::traits::{AesGcm, Device, EcDsa}; 34 use authgraph_wire::{ErrorCode, SESSION_ID_LEN}; 35 use coset::CborSerializable; 36 use coset::{iana, CoseKey}; 37 use diced_open_dice::derive_cdi_leaf_priv; 38 use explicitkeydice::OwnedDiceArtifactsWithExplicitKey; 39 40 /// Implementation of `authgraph_core::traits::Device` required for configuring the local 41 /// `AuthGraphParticipant` for client. 42 #[derive(Clone)] 43 pub struct AgDevice { 44 per_boot_key: AesKey, 45 identity: (EcSignKey, Identity), 46 expected_peer_key: Option<CoseKey>, 47 } 48 49 impl AgDevice { 50 const AG_KE_VERSION: i32 = AG_KEY_EXCHANGE_PROTOCOL_VERSION_1; 51 /// Create a new `AgDevice`, using dice_artifacts for `Identity`. new( dice_artifacts: &OwnedDiceArtifactsWithExplicitKey, expected_peer_key: Option<CoseKey>, ) -> Result<Self, AgError>52 pub fn new( 53 dice_artifacts: &OwnedDiceArtifactsWithExplicitKey, 54 expected_peer_key: Option<CoseKey>, 55 ) -> Result<Self, AgError> { 56 let cdi_leaf_priv = derive_cdi_leaf_priv(dice_artifacts) 57 .map_err(|_| ag_err!(InternalError, "Failed to get private key"))?; 58 let identity = Identity { 59 version: IDENTITY_VERSION, 60 cert_chain: CertChain::from_slice( 61 dice_artifacts 62 .explicit_key_dice_chain() 63 .ok_or(ag_err!(InternalError, "Dice chain missing"))?, 64 )?, 65 policy: None, 66 }; 67 68 let per_boot_key = BoringAes {}.generate_key(&mut BoringRng {})?; 69 70 Ok(Self { 71 per_boot_key, 72 identity: ( 73 EcSignKey::Ed25519( 74 // AuthGraph supports storing only the Ed25519 "seed", which is first 32 bytes 75 // of the private key, instead of the full private key. 76 cdi_leaf_priv.as_array()[0..CURVE25519_PRIV_KEY_LEN].try_into().map_err( 77 |e| { 78 ag_err!( 79 InternalError, 80 "Failed to construct private signing key {:?}", 81 e 82 ) 83 }, 84 )?, 85 ), 86 identity, 87 ), 88 expected_peer_key, 89 }) 90 } 91 } 92 93 impl Device for AgDevice { get_or_create_per_boot_key( &self, _aes: &dyn traits::AesGcm, _rng: &mut dyn traits::Rng, ) -> Result<AesKey, AgError>94 fn get_or_create_per_boot_key( 95 &self, 96 _aes: &dyn traits::AesGcm, 97 _rng: &mut dyn traits::Rng, 98 ) -> Result<AesKey, AgError> { 99 Ok(self.per_boot_key.clone()) 100 } 101 get_per_boot_key(&self) -> Result<AesKey, AgError>102 fn get_per_boot_key(&self) -> Result<AesKey, AgError> { 103 Ok(self.per_boot_key.clone()) 104 } 105 get_identity(&self) -> Result<(Option<EcSignKey>, Identity), AgError>106 fn get_identity(&self) -> Result<(Option<EcSignKey>, Identity), AgError> { 107 let (sign_key, identity) = self.identity.clone(); 108 Ok((Some(sign_key), identity)) 109 } 110 get_cose_sign_algorithm(&self) -> Result<iana::Algorithm, AgError>111 fn get_cose_sign_algorithm(&self) -> Result<iana::Algorithm, AgError> { 112 // Microdroid Manager uses EdDSA as the signing algorithm while doing DICE derivation. 113 Ok(iana::Algorithm::EdDSA) 114 } 115 sign_data(&self, _ecdsa: &dyn traits::EcDsa, _data: &[u8]) -> Result<Vec<u8>, AgError>116 fn sign_data(&self, _ecdsa: &dyn traits::EcDsa, _data: &[u8]) -> Result<Vec<u8>, AgError> { 117 // Since the private signing key is already returned in the `get_identity` method of this 118 // implementation, this method can be marked `Unimplemented`. 119 Err(ag_err!(Unimplemented, "Unexpected signing request when the signing key available")) 120 } 121 evaluate_identity( &self, _latest_identity: &Identity, _previous_identity: &Identity, ) -> Result<IdentityVerificationDecision, AgError>122 fn evaluate_identity( 123 &self, 124 _latest_identity: &Identity, 125 _previous_identity: &Identity, 126 ) -> Result<IdentityVerificationDecision, AgError> { 127 // AuthGraph Key Exchange protocol does not require this. Additionally, Secretkeeper 128 // clients create fresh, short-lived sessions and their identity does not change within 129 // a session. 130 Err(ag_err!(Unimplemented, "Not required")) 131 } 132 get_version(&self) -> i32133 fn get_version(&self) -> i32 { 134 Self::AG_KE_VERSION 135 } 136 record_shared_sessions( &mut self, _peer_identity: &Identity, _session_id: &[u8; SESSION_ID_LEN], _shared_keys: &[Vec<u8>; 2], _sha256: &dyn traits::Sha256, ) -> Result<(), AgError>137 fn record_shared_sessions( 138 &mut self, 139 _peer_identity: &Identity, 140 _session_id: &[u8; SESSION_ID_LEN], 141 _shared_keys: &[Vec<u8>; 2], 142 _sha256: &dyn traits::Sha256, 143 ) -> Result<(), AgError> { 144 // There are alternative ways of recording the shared session such as inspecting the output 145 // of KE methods. 146 Ok(()) 147 } 148 validate_peer_identity( &self, identity: &Identity, ecdsa: &dyn EcDsa, ) -> Result<EcVerifyKey, Error>149 fn validate_peer_identity( 150 &self, 151 identity: &Identity, 152 ecdsa: &dyn EcDsa, 153 ) -> Result<EcVerifyKey, Error> { 154 if identity.cert_chain.dice_cert_chain.is_some() { 155 return Err(ag_err!(InvalidPeerKeKey, "Expected peer's DICE chain to be None")); 156 } 157 let root_key = identity.validate(ecdsa)?; 158 159 if let Some(expected_key) = &self.expected_peer_key { 160 // Do bit by bit comparison of the keys. This assumes the 2 keys are equivalently 161 // canonicalized 162 if root_key.get_key_ref() != expected_key { 163 return Err(ag_err!( 164 InvalidPeerKeKey, 165 "Peer identity did not match the expected identity" 166 )); 167 } 168 } 169 Ok(root_key) 170 } 171 validate_shared_sessions( &self, _peer_identity: &Identity, _session_id: &[u8; SESSION_ID_LEN], _shared_keys: &[Vec<u8>], _sha256: &dyn traits::Sha256, ) -> Result<(), Error>172 fn validate_shared_sessions( 173 &self, 174 _peer_identity: &Identity, 175 _session_id: &[u8; SESSION_ID_LEN], 176 _shared_keys: &[Vec<u8>], 177 _sha256: &dyn traits::Sha256, 178 ) -> Result<(), Error> { 179 // Currently, there is no use of validation of shared session to application protocol. 180 Ok(()) 181 } 182 } 183