charon-tkm: Delegate encryption/decryption of IKE traffic to TKM
Co-authored-by: Tobias Brunner <tobias@strongswan.org>
This commit is contained in:
parent
6537be9c8d
commit
22e7900718
|
@ -0,0 +1,245 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Tobias Brunner
|
||||
* HSR Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* Copyright (C) 2020 secunet Security Networks AG
|
||||
* Copyright (C) 2020 Stefan Berghofer
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <crypto/iv/iv_gen.h>
|
||||
#include <tkm/constants.h>
|
||||
#include <tkm/client.h>
|
||||
|
||||
#include "tkm_aead.h"
|
||||
#include "tkm_utils.h"
|
||||
|
||||
typedef struct private_aead_t private_aead_t;
|
||||
|
||||
/**
|
||||
* AEAD implementation using TKM
|
||||
*/
|
||||
struct private_aead_t {
|
||||
|
||||
/**
|
||||
* Public interface
|
||||
* */
|
||||
aead_t public;
|
||||
|
||||
/**
|
||||
* Internal IV generator for TKM
|
||||
*/
|
||||
iv_gen_t iv_gen;
|
||||
|
||||
/**
|
||||
* ISA context id
|
||||
*/
|
||||
isa_id_type isa_ctx_id;
|
||||
|
||||
/**
|
||||
* Block length of encryption algorithm
|
||||
*/
|
||||
block_len_type block_len;
|
||||
|
||||
/**
|
||||
* Length of integrity check value
|
||||
*/
|
||||
icv_len_type icv_len;
|
||||
|
||||
/**
|
||||
* Length of initialization vector
|
||||
*/
|
||||
iv_len_type iv_len;
|
||||
};
|
||||
|
||||
METHOD(iv_gen_t, get_iv, bool,
|
||||
iv_gen_t *this, uint64_t seq, size_t size, uint8_t *buffer)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
METHOD(iv_gen_t, allocate_iv, bool,
|
||||
iv_gen_t *this, uint64_t seq, size_t size, chunk_t *chunk)
|
||||
{
|
||||
*chunk = chunk_alloc(size);
|
||||
return get_iv(this, seq, chunk->len, chunk->ptr);
|
||||
}
|
||||
|
||||
METHOD(aead_t, encrypt, bool,
|
||||
private_aead_t *this, chunk_t plain, chunk_t assoc,
|
||||
chunk_t iv, chunk_t *encrypted)
|
||||
{
|
||||
aad_plain_type aad_plain;
|
||||
iv_encrypted_icv_type iv_encrypted_icv;
|
||||
result_type res;
|
||||
|
||||
aad_plain = (aad_plain_type){
|
||||
.size = assoc.len + plain.len,
|
||||
};
|
||||
if (aad_plain.size > sizeof(aad_plain.data))
|
||||
{
|
||||
DBG1(DBG_IKE, "%u exceeds buffer size %u, encryption failed (isa: "
|
||||
"%llu)", aad_plain.size, sizeof(aad_plain.data), this->isa_ctx_id);
|
||||
return FALSE;
|
||||
}
|
||||
memcpy(aad_plain.data, assoc.ptr, assoc.len);
|
||||
memcpy(aad_plain.data + assoc.len, plain.ptr, plain.len);
|
||||
|
||||
res = ike_isa_encrypt(this->isa_ctx_id, assoc.len, aad_plain,
|
||||
&iv_encrypted_icv);
|
||||
if (res != TKM_OK)
|
||||
{
|
||||
DBG1(DBG_IKE, "encryption failed (isa: %llu)", this->isa_ctx_id);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (encrypted)
|
||||
{
|
||||
sequence_to_chunk(iv_encrypted_icv.data, iv_encrypted_icv.size,
|
||||
encrypted);
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(plain.ptr, iv_encrypted_icv.data + iv.len,
|
||||
iv_encrypted_icv.size - iv.len);
|
||||
}
|
||||
memcpy(iv.ptr, iv_encrypted_icv.data, iv.len);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
METHOD(aead_t, decrypt, bool,
|
||||
private_aead_t *this, chunk_t encrypted, chunk_t assoc, chunk_t iv,
|
||||
chunk_t *plain)
|
||||
{
|
||||
aad_iv_encrypted_icv_type aad_iv_encrypted_icv;
|
||||
decrypted_type decrypted;
|
||||
result_type res;
|
||||
|
||||
aad_iv_encrypted_icv = (aad_iv_encrypted_icv_type){
|
||||
.size = assoc.len + iv.len + encrypted.len,
|
||||
};
|
||||
if (aad_iv_encrypted_icv.size > sizeof(aad_iv_encrypted_icv.data))
|
||||
{
|
||||
DBG1(DBG_IKE, "%u exceeds buffer size %u, decryption failed (isa: "
|
||||
"%llu)", aad_iv_encrypted_icv.size,
|
||||
sizeof(aad_iv_encrypted_icv.data), this->isa_ctx_id);
|
||||
return FALSE;
|
||||
}
|
||||
memcpy(aad_iv_encrypted_icv.data, assoc.ptr, assoc.len);
|
||||
memcpy(aad_iv_encrypted_icv.data + assoc.len, iv.ptr, iv.len);
|
||||
memcpy(aad_iv_encrypted_icv.data + assoc.len + iv.len, encrypted.ptr,
|
||||
encrypted.len);
|
||||
|
||||
res = ike_isa_decrypt(this->isa_ctx_id, assoc.len, aad_iv_encrypted_icv,
|
||||
&decrypted);
|
||||
if (res != TKM_OK)
|
||||
{
|
||||
DBG1(DBG_IKE, "decryption failed (isa: %llu)", this->isa_ctx_id);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (plain)
|
||||
{
|
||||
sequence_to_chunk(decrypted.data, decrypted.size, plain);
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(encrypted.ptr, decrypted.data, decrypted.size);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
METHOD(aead_t, get_block_size, size_t,
|
||||
private_aead_t *this)
|
||||
{
|
||||
return this->block_len;
|
||||
}
|
||||
|
||||
METHOD(aead_t, get_icv_size, size_t,
|
||||
private_aead_t *this)
|
||||
{
|
||||
return this->icv_len;
|
||||
}
|
||||
|
||||
METHOD(aead_t, get_iv_size, size_t,
|
||||
private_aead_t *this)
|
||||
{
|
||||
return this->iv_len;
|
||||
}
|
||||
|
||||
METHOD(aead_t, get_iv_gen, iv_gen_t*,
|
||||
private_aead_t *this)
|
||||
{
|
||||
return &this->iv_gen;
|
||||
}
|
||||
|
||||
METHOD(aead_t, get_key_size, size_t,
|
||||
private_aead_t *this)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
METHOD(aead_t, set_key, bool,
|
||||
private_aead_t *this, chunk_t key)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
METHOD(aead_t, destroy, void,
|
||||
private_aead_t *this)
|
||||
{
|
||||
free(this);
|
||||
}
|
||||
|
||||
/*
|
||||
* Described in header
|
||||
*/
|
||||
aead_t *tkm_aead_create(isa_id_type isa_ctx_id, block_len_type block_len,
|
||||
icv_len_type icv_len, iv_len_type iv_len)
|
||||
{
|
||||
private_aead_t *aead;
|
||||
|
||||
INIT(aead,
|
||||
.public = {
|
||||
.encrypt = _encrypt,
|
||||
.decrypt = _decrypt,
|
||||
.get_block_size = _get_block_size,
|
||||
.get_icv_size = _get_icv_size,
|
||||
.get_iv_size = _get_iv_size,
|
||||
.get_iv_gen = _get_iv_gen,
|
||||
.get_key_size = _get_key_size,
|
||||
.set_key = _set_key,
|
||||
.destroy = _destroy,
|
||||
},
|
||||
.iv_gen = {
|
||||
.get_iv = _get_iv,
|
||||
.allocate_iv = _allocate_iv,
|
||||
.destroy = (void *)nop,
|
||||
},
|
||||
.isa_ctx_id = isa_ctx_id,
|
||||
.block_len = block_len,
|
||||
.icv_len = icv_len,
|
||||
.iv_len = iv_len,
|
||||
);
|
||||
|
||||
return &aead->public;
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (C) 2020 secunet Security Networks AG
|
||||
* Copyright (C) 2020 Stefan Berghofer
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup tkm-aead aead
|
||||
* @{ @ingroup tkm
|
||||
*/
|
||||
|
||||
#ifndef TKM_AEAD_H_
|
||||
#define TKM_AEAD_H_
|
||||
|
||||
typedef struct tkm_aead_t tkm_aead_t;
|
||||
|
||||
#include <crypto/aead.h>
|
||||
#include <tkm/types.h>
|
||||
|
||||
/**
|
||||
* Create an AEAD implementation providing encryption and integrity protection
|
||||
* using TKM.
|
||||
*
|
||||
* @param isa_ctx_id id of ISA context to use for encryption/decryption
|
||||
* @param block_len block length of encryption algorithm
|
||||
* @param icv_len length of integrity check value
|
||||
* @param iv_len length of initialization vector
|
||||
* @return created aead_t object
|
||||
*/
|
||||
aead_t *tkm_aead_create(isa_id_type isa_ctx_id, block_len_type block_len,
|
||||
icv_len_type icv_len, iv_len_type iv_len);
|
||||
|
||||
#endif /** TKM_AEAD_H_ @}*/
|
|
@ -25,6 +25,7 @@
|
|||
#include "tkm_utils.h"
|
||||
#include "tkm_diffie_hellman.h"
|
||||
#include "tkm_keymat.h"
|
||||
#include "tkm_aead.h"
|
||||
|
||||
typedef struct private_tkm_keymat_t private_tkm_keymat_t;
|
||||
|
||||
|
@ -44,14 +45,9 @@ struct private_tkm_keymat_t {
|
|||
bool initiator;
|
||||
|
||||
/**
|
||||
* Inbound AEAD.
|
||||
* AEAD implementation.
|
||||
*/
|
||||
aead_t *aead_in;
|
||||
|
||||
/**
|
||||
* Outbound AEAD.
|
||||
*/
|
||||
aead_t *aead_out;
|
||||
aead_t *aead;
|
||||
|
||||
/**
|
||||
* ISA context id.
|
||||
|
@ -79,91 +75,6 @@ struct private_tkm_keymat_t {
|
|||
hash_algorithm_set_t *hash_algorithms;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create AEAD transforms from given key chunks.
|
||||
*
|
||||
* @param in inbound AEAD transform to allocate, NULL if failed
|
||||
* @param out outbound AEAD transform to allocate, NULL if failed
|
||||
* @param sk_ai SK_ai key chunk
|
||||
* @param sk_ar SK_ar key chunk
|
||||
* @param sk_ei SK_ei key chunk
|
||||
* @param sk_er SK_er key chunk
|
||||
* @param enc_alg encryption algorithm to use
|
||||
* @param int_alg integrity algorithm to use
|
||||
* @param key_size encryption key size in bytes
|
||||
* @param initiator TRUE if initiator
|
||||
*/
|
||||
static void aead_create_from_keys(aead_t **in, aead_t **out,
|
||||
const chunk_t * const sk_ai, const chunk_t * const sk_ar,
|
||||
const chunk_t * const sk_ei, const chunk_t * const sk_er,
|
||||
const uint16_t enc_alg, const uint16_t int_alg,
|
||||
const uint16_t key_size, bool initiator)
|
||||
{
|
||||
*in = *out = NULL;
|
||||
signer_t *signer_i, *signer_r;
|
||||
crypter_t *crypter_i, *crypter_r;
|
||||
iv_gen_t *ivg_i, *ivg_r;
|
||||
|
||||
signer_i = lib->crypto->create_signer(lib->crypto, int_alg);
|
||||
signer_r = lib->crypto->create_signer(lib->crypto, int_alg);
|
||||
if (signer_i == NULL || signer_r == NULL)
|
||||
{
|
||||
DBG1(DBG_IKE, "%N %N not supported!",
|
||||
transform_type_names, INTEGRITY_ALGORITHM,
|
||||
integrity_algorithm_names, int_alg);
|
||||
return;
|
||||
}
|
||||
crypter_i = lib->crypto->create_crypter(lib->crypto, enc_alg, key_size);
|
||||
crypter_r = lib->crypto->create_crypter(lib->crypto, enc_alg, key_size);
|
||||
if (crypter_i == NULL || crypter_r == NULL)
|
||||
{
|
||||
signer_i->destroy(signer_i);
|
||||
signer_r->destroy(signer_r);
|
||||
DBG1(DBG_IKE, "%N %N (key size %d) not supported!",
|
||||
transform_type_names, ENCRYPTION_ALGORITHM,
|
||||
encryption_algorithm_names, enc_alg, key_size);
|
||||
return;
|
||||
}
|
||||
|
||||
DBG4(DBG_IKE, "Sk_ai %B", sk_ai);
|
||||
if (!signer_i->set_key(signer_i, *sk_ai))
|
||||
{
|
||||
return;
|
||||
}
|
||||
DBG4(DBG_IKE, "Sk_ar %B", sk_ar);
|
||||
if (!signer_r->set_key(signer_r, *sk_ar))
|
||||
{
|
||||
return;
|
||||
}
|
||||
DBG4(DBG_IKE, "Sk_ei %B", sk_ei);
|
||||
if (!crypter_i->set_key(crypter_i, *sk_ei))
|
||||
{
|
||||
return;
|
||||
}
|
||||
DBG4(DBG_IKE, "Sk_er %B", sk_er);
|
||||
if (!crypter_r->set_key(crypter_r, *sk_er))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ivg_i = iv_gen_create_for_alg(enc_alg);
|
||||
ivg_r = iv_gen_create_for_alg(enc_alg);
|
||||
if (!ivg_i || !ivg_r)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (initiator)
|
||||
{
|
||||
*in = aead_create(crypter_r, signer_r, ivg_r);
|
||||
*out = aead_create(crypter_i, signer_i, ivg_i);
|
||||
}
|
||||
else
|
||||
{
|
||||
*in = aead_create(crypter_i, signer_i, ivg_i);
|
||||
*out = aead_create(crypter_r, signer_r, ivg_r);
|
||||
}
|
||||
}
|
||||
|
||||
METHOD(keymat_t, get_version, ike_version_t,
|
||||
private_tkm_keymat_t *this)
|
||||
{
|
||||
|
@ -189,12 +100,14 @@ METHOD(keymat_v2_t, derive_ike_keys, bool,
|
|||
{
|
||||
uint16_t enc_alg, int_alg, key_size;
|
||||
uint64_t nc_id, spi_loc, spi_rem;
|
||||
chunk_t *nonce, c_ai, c_ar, c_ei, c_er;
|
||||
chunk_t *nonce;
|
||||
tkm_diffie_hellman_t *tkm_dh;
|
||||
dh_id_type dh_id;
|
||||
nonce_type nonce_rem;
|
||||
result_type res;
|
||||
key_type sk_ai, sk_ar, sk_ei, sk_er;
|
||||
block_len_type block_len;
|
||||
icv_len_type icv_len;
|
||||
iv_len_type iv_len;
|
||||
|
||||
/* Check encryption and integrity algorithms */
|
||||
if (!proposal->get_algorithm(proposal, ENCRYPTION_ALGORITHM, &enc_alg,
|
||||
|
@ -266,7 +179,7 @@ METHOD(keymat_v2_t, derive_ike_keys, bool,
|
|||
"spi_rem: %llx)", nc_id, dh_id, spi_loc, spi_rem);
|
||||
res = ike_isa_create(this->isa_ctx_id, this->ae_ctx_id, 1, dh_id, nc_id,
|
||||
nonce_rem, this->initiator, spi_loc, spi_rem,
|
||||
&sk_ai, &sk_ar, &sk_ei, &sk_er);
|
||||
&block_len, &icv_len, &iv_len);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -291,8 +204,8 @@ METHOD(keymat_v2_t, derive_ike_keys, bool,
|
|||
this->ae_ctx_id = isa_info.ae_id;
|
||||
res = ike_isa_create_child(this->isa_ctx_id, isa_info.parent_isa_id, 1,
|
||||
dh_id, nc_id, nonce_rem, this->initiator,
|
||||
spi_loc, spi_rem, &sk_ai, &sk_ar, &sk_ei,
|
||||
&sk_er);
|
||||
spi_loc, spi_rem, &block_len, &icv_len,
|
||||
&iv_len);
|
||||
chunk_free(&rekey_skd);
|
||||
}
|
||||
|
||||
|
@ -302,25 +215,7 @@ METHOD(keymat_v2_t, derive_ike_keys, bool,
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
sequence_to_chunk(sk_ai.data, sk_ai.size, &c_ai);
|
||||
sequence_to_chunk(sk_ar.data, sk_ar.size, &c_ar);
|
||||
sequence_to_chunk(sk_ei.data, sk_ei.size, &c_ei);
|
||||
sequence_to_chunk(sk_er.data, sk_er.size, &c_er);
|
||||
|
||||
aead_create_from_keys(&this->aead_in, &this->aead_out, &c_ai, &c_ar, &c_ei,
|
||||
&c_er, enc_alg, int_alg, key_size / 8,
|
||||
this->initiator);
|
||||
|
||||
chunk_clear(&c_ai);
|
||||
chunk_clear(&c_ar);
|
||||
chunk_clear(&c_ei);
|
||||
chunk_clear(&c_er);
|
||||
|
||||
if (!this->aead_in || !this->aead_out)
|
||||
{
|
||||
DBG1(DBG_IKE, "could not initialize AEAD transforms");
|
||||
return FALSE;
|
||||
}
|
||||
this->aead = tkm_aead_create(this->isa_ctx_id, block_len, icv_len, iv_len);
|
||||
|
||||
/* TODO: Add failure handler (see keymat_v2.c) */
|
||||
|
||||
|
@ -380,7 +275,7 @@ METHOD(keymat_v2_t, derive_child_keys, bool,
|
|||
METHOD(keymat_t, get_aead, aead_t*,
|
||||
private_tkm_keymat_t *this, bool in)
|
||||
{
|
||||
return in ? this->aead_in : this->aead_out;
|
||||
return this->aead;
|
||||
}
|
||||
|
||||
METHOD(keymat_v2_t, get_auth_octets, bool,
|
||||
|
@ -474,8 +369,7 @@ METHOD(keymat_t, destroy, void,
|
|||
}
|
||||
|
||||
DESTROY_IF(this->hash_algorithms);
|
||||
DESTROY_IF(this->aead_in);
|
||||
DESTROY_IF(this->aead_out);
|
||||
DESTROY_IF(this->aead);
|
||||
chunk_free(&this->auth_payload);
|
||||
chunk_free(&this->other_init_msg);
|
||||
free(this);
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/make
|
||||
|
||||
PKG = spark-crypto
|
||||
SRC = https://git.codelabs.ch/spark-crypto.git
|
||||
REV = c97939b6cdd5e5f19847cf8d1abb7575e1c01df7
|
||||
|
||||
DESTDIR = /usr/local/ada/lib/gnat
|
||||
|
||||
all: install
|
||||
|
||||
.$(PKG)-cloned:
|
||||
[ -d $(PKG) ] || git clone $(SRC) $(PKG)
|
||||
@touch $@
|
||||
|
||||
.$(PKG)-checkout-$(REV): .$(PKG)-cloned
|
||||
cd $(PKG) && git fetch && git checkout $(REV)
|
||||
@rm -f .$(PKG)-checkout-* && touch $@
|
||||
|
||||
.$(PKG)-built-$(REV): .$(PKG)-checkout-$(REV)
|
||||
cd $(PKG) && make NO_SPARK=1 NO_TESTS=1 NO_APIDOC=1
|
||||
@rm -f .$(PKG)-built-* && touch $@
|
||||
|
||||
install: .$(PKG)-built-$(REV)
|
||||
cd $(PKG) && make NO_SPARK=1 NO_TESTS=1 NO_APIDOC=1 DESTDIR=$(DESTDIR) install
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
PKG = tkm-rpc
|
||||
SRC = https://git.codelabs.ch/git/$(PKG).git
|
||||
REV = 1235905c5c6fad5df1eecb6ba0447d5722753203
|
||||
REV = a681aa8694412f16a44a7fba2eeb67cb3d43caf6
|
||||
|
||||
PREFIX = /usr/local/ada
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
PKG = tkm
|
||||
SRC = https://git.codelabs.ch/git/$(PKG).git
|
||||
REV = b99aeb158b7701ea4a77184bff5ff38f8e26013a
|
||||
REV = fadff7fd8c454ae46177924fde56600081ddf4d5
|
||||
|
||||
export ADA_PROJECT_PATH=/usr/local/ada/lib/gnat
|
||||
|
||||
|
|
Loading…
Reference in New Issue