Skip to content

Port the cryptocell 310 cmac driver #10947

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

Merged
merged 7 commits into from
Jul 29, 2019

Conversation

RonEld
Copy link
Contributor

@RonEld RonEld commented Jul 2, 2019

Description

Add support for CC310 CMAC driver returning
MBEDTLS_ERR_PLATFORM_FEATURE_UNSUPPORTED for key size other than 128 bits,
and for crypto algorithms other than AES( e.g. DES).
According to the generated map file, using GCC_ARM:
ROM: 640 bytes smaller
RAM: No Change
Benchmark information, built with GCC_ARM:

  AES-CMAC-128             :       7255 KB/s                                                                                               
  AES-CMAC-192             :  Feature unsupported                                                                                          
  AES-CMAC-256             :  Feature unsupported                                                                                          
  AES-CMAC-PRF-128         :  Feature unsupported 

Pull request type

[ ] Fix
[ ] Refactor
[ x] Target update
[ ] Functionality change
[ ] Docs update
[ ] Test update
[ ] Breaking change

Reviewers

@ARMmbed/mbed-os-crypto

Release Notes

This is a target update, adding hw accelerated CMAC, however, it can also be considered as a breaking change, as it removes support for key sizes other than 128 bit keys, and removes support for DES CMAC.

Add support for CC310 CMAC driver returning
`MBEDTLS_ERR_PLATFORM_FEATURE_UNSUPPORTED` for key size other than 128 bits,
and for crypto algorithms other than AES( e.g. DES).
cmac_ctx->CC_keySizeInBytes = ( keybits / 8 );
memcpy( cmac_ctx->CC_Key, key, cmac_ctx->CC_keySizeInBytes );
}
break;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest removing the {} or putting break inside the {}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would advocate for removing all the brackets as the cases in this switch statement are very simple.

@@ -0,0 +1,50 @@
/*
* aes_alt.h
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should say cmac_alt.h here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, a copy\paste error

goto exit;
}

if( SaSi_AesFinish( &ctx.cmac_ctx->CC_Context, ilen, ( uint8_t * ) input,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not call mbedtls_cipher_cmac_update() and `mbedtls_cipher_cmac_finish()?

If AES_ALT is set, won't the software CMAC implementation use it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be done, but it adds additional redundant code. In this API, it's a one call CMAC functionality, without storing unprocessed_block, and call directly to the CC AES driver to calculate the CMAC.
The alternative implementation of mbedtls_cipher_cmac_update() and mbedtls_cipher_cmac_finish() has support for the unprocessed data, to support sequential calls with sizes that are not multiple of AES block size

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

@ciarmcom ciarmcom requested review from a team July 2, 2019 17:00
@ciarmcom
Copy link
Member

ciarmcom commented Jul 2, 2019

@RonEld, thank you for your changes.
@ARMmbed/mbed-os-crypto @ARMmbed/mbed-os-maintainers please review.

Copy link

@andresag01 andresag01 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RonEld: I did an initial review of the changes and wrote down a number of comments/questions. Please let me know what you think

*/
cmac_ctx = mbedtls_calloc( 1, sizeof( mbedtls_cmac_context_t ) );
if( cmac_ctx == NULL )
return( MBEDTLS_ERR_CIPHER_ALLOC_FAILED );

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to allocate this before making the remaining keybits check? Perhaps it would be more optimal to move the allocation into the case 128: or move the switch check before this allocation.

}

ctx->cmac_ctx = cmac_ctx;
return 0;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add brackets around the argument to return

cmac_ctx->CC_keySizeInBytes = ( keybits / 8 );
memcpy( cmac_ctx->CC_Key, key, cmac_ctx->CC_keySizeInBytes );
}
break;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would advocate for removing all the brackets as the cases in this switch statement are very simple.

int ret = 0;
SaSiAesUserKeyData_t CC_KeyData;
if( SaSi_AesInit( &cmac_ctx->CC_Context, SASI_AES_ENCRYPT,
SASI_AES_MODE_CMAC, SASI_AES_PADDING_NONE) != 0 )

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style: PLease add space before )

}

CC_KeyData.pKey = cmac_ctx->CC_Key;
CC_KeyData.keySize = cmac_ctx->CC_keySizeInBytes;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, this is just a temporary struct with a little bit of metadata a pointer to the key. Why not just make that struct part of the mbedtls_cmac_context_t? Does SaSiAesUserKeyData_t contain a lot more stuff that we do not want?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This struct only holds the pointer to the key and the key size. It is used being set in one CC function so I don't see an added value of putting it as part of the context structure.
In addition, if you have this structure as part of the context, which will only point to data being given by user, it might point to data that doesn't exist anymore

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

* \return \c 0 on success.
* \return #MBEDTLS_ERR_MD_BAD_INPUT_DATA
* if parameter verification fails.
*/

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have this documentation here? Surely its the same as in the header files. But probably nobody is going to see it as its inside this source file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A mistake. I will remove

}

exit:
if( SaSi_AesFree( &cmac_ctx->CC_Context ) != 0 && ret == 0 )

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this function need to be called somewhere else? What happens if I am the application programmer and decide that somewhere in the middle of a computation I no longer want to continue doing CMAC (even though all the calls to starts and update have terminated correctly). How do I release the resources from that CMAC context and the accelerator?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there isn't any terminate function to deallocate all of the cmac resources, I don't see another location

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, but how does this API work then? how does the user release the resources from a context? What happens when I decided that I do not want to continue a computation? Who releases the dynamic memory allocated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, I don't have a good answer, but I really don't see another option of where to clear the resources, the way our cmac is implemented.
The cmac context is cleared in the cipher module, which I don't think is correct, but this is the way it behaves.
Anyway, as mentioned in mbedtls_cipher_free , this is not a real memory leak, as the CC function only clears the context,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To free the resources, reset should be called on the context. SaSi_AesFree should be called as part of mbedtls_cmac_reset()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if you look at implementation of mbedtls_cipher_cmac(), you will see that mbedtls_cmac_reset() is not called, causing resources not to be freed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a bug



exit:
if( SaSi_AesFree( &ctx.cmac_ctx->CC_Context ) != 0 && ret == 0 )

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if we fail before calling init_cc? Is that ok/acceptable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing will really happen, because SaSi_AesFree() mostly memsets to zero. However I see your point, as this is not correct behavior. i'll fix this

/*
* aes_alt.h
*
* Copyright (C) 2018, ARM Limited, All Rights Reserved

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the dates and branding of this license header are out-of-date

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, copy\paste error as well

cmac_ctx->unprocessed_len = 0;
mbedtls_platform_zeroize( cmac_ctx->unprocessed_block,
sizeof( cmac_ctx->unprocessed_block ) );

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why doesnt this function need to reset the accelerator and clear the initialized flag.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because:

 * \brief               This function prepares the authentication of another
 *                      message with the same key as the previous CMAC
 *                      operation.

Since same key is used, no need to reset the accelerator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

Ron Eldor added 3 commits July 8, 2019 18:10
Fix minor style fixes and typos:
1. Change file name to correct one.
2. Change copyright year.
3. Insert whitespaces before and after paranthesis.
4. Put `*` next to pointer name.
FIx some functionality issues for better visibility:
1. Allocate the context only for 128 bit key
2. Change oreder of freeing the resources.
Have the alternative cmac undefined by default,
in order not to break backwards compatability.
@RonEld
Copy link
Contributor Author

RonEld commented Jul 8, 2019

I made the alternative cmac optional. I will make a separate PR, updating the readme file

mbedtls_platform_zeroize( cmac_ctx->unprocessed_block,
sizeof( cmac_ctx->unprocessed_block ) );

return( 0 );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reset() should also call SaSi_AesFree, to free up the cmac_ctx->CC_Context. I'm not sure if we need to check that CC_Context was initialized (as we initialize it in update()) before we reset. Alternatively, we could consider allocating the CC_Context in starts().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about allocating in starts(), but then in use cases of starts() ->update()->finish() ->reset() update()->finish() we might fail

Copy link
Contributor

@Patater Patater Jul 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, both starts and reset would need to allocate a new CC_Context context as well in that flow. But, reset is supposed to free all resources, not make new ones. This means starts is not a good place to allocate resources, agreed.

goto exit;
}

if( SaSi_AesFinish( &ctx.cmac_ctx->CC_Context, ilen, ( uint8_t * ) input,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

extern "C" {
#endif

struct mbedtls_cmac_context_t
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to typedef this so it works with C? It looks like perhaps this header was only tested with C++ builds.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was tested with our on target tests, which I believe is in c

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mbed OS always builds with C++, so it's always C++ for on-target testing. We should use typedef here if we want to be C compatible. Otherwise, when you later refer to mbedtls_cmac_context_t blah; instead of struct mbedtls_cmac_context_t blah, you'll get compilation failures. I see many places in this PR where we don't say struct mbedtls_cmac_context_t.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, I'll change but this implementation is for Mbed OS anyway

Ron Eldor added 2 commits July 23, 2019 16:05
Initiate the CC context in the starts function and in the reset.
In the reset function, free aes context before.
Free the context in the finish function and reset function.
Make the cmac context a typedef, to be compatible with c code.
@RonEld RonEld force-pushed the cc310_cmac_porting branch from f89eaab to 4e29c8f Compare July 23, 2019 13:05
@RonEld
Copy link
Contributor Author

RonEld commented Jul 23, 2019

@Patater I addressed your comments


if( ctx == NULL || ctx->cipher_info == NULL )
return( MBEDTLS_ERR_CIPHER_BAD_INPUT_DATA );

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove extra newline

exit:
if( ret != 0 )
{
SaSi_AesFree( &cmac_ctx->CC_Context );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it always safe to call SaSi_AesFree()? or do we need to check cmac_ctx->is_cc_initiated == 1 like we do in mbedtls_cipher_cmac_finish()? You might want to make deinit_cc() to avoid repeating the same is_cc_initiated check everywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it always safe to call SaSi_AesFree()?

Well, yes, because internally it just memsets the buffer to zero, but It's better to change as you suggested. It's a residue from when we initiated the context also in update

ret = mbedtls_cipher_cmac_starts( &ctx, key, keylen );
if( ret != 0 )
goto exit;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove extra newline.

Add a deinit function that will be called and check inside
whether context is initialized. This function is called for
freeing the CC context, instead of every time check that it's
initizliaed and free it.
@RonEld
Copy link
Contributor Author

RonEld commented Jul 24, 2019

@Patater I have addressed your comments. Please review

Copy link
Contributor

@Patater Patater left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Please comment what testing you've done, now that this has undergone some changes during code review.

@RonEld
Copy link
Contributor Author

RonEld commented Jul 25, 2019

@Patater

Please comment what testing you've done, now that this has undergone some changes during code review.

I have run the cmac on target test features-mbedtls-tests-mbedtls-test_suite_cmac with ARMmbed/mbed-crypto#162 incorporated :

mbedgt: test on hardware with target id: 1102000044203120414a4e39313031313730303797969903
mbedgt: test suite 'features-mbedtls-tests-mbedtls-test_suite_cmac' .................................. OK in 27.96 sec
	test case: 'CMAC Multiple Blocks #1 - Multiple 8 byte blocks' ................................ OK in 0.00 sec
	test case: 'CMAC Multiple Blocks #2 - Multiple 16 byte blocks' ............................... OK in 0.00 sec
	test case: 'CMAC Multiple Blocks #3 - Multiple variable sized blocks' ........................ OK in 0.00 sec
	test case: 'CMAC Multiple Blocks #4 - Multiple 8 byte blocks with gaps' ...................... OK in 0.00 sec
	test case: 'CMAC Multiple Operations, same key #1 - Empty, empty' ............................ SKIPPED in 0.00 sec
	test case: 'CMAC Multiple Operations, same key #2 - Empty, 64 byte block' .................... SKIPPED in 0.00 sec
	test case: 'CMAC Multiple Operations, same key #3 - variable byte blocks' .................... SKIPPED in 0.00 sec
	test case: 'CMAC Single Blocks #1 - Empty block, no updates' ................................. OK in 0.00 sec
	test case: 'CMAC Single Blocks #2 - Single 16 byte block' .................................... OK in 0.00 sec
	test case: 'CMAC Single Blocks #3 - Single 64 byte block' .................................... OK in 0.00 sec
	test case: 'CMAC init #1 AES-128: OK' ........................................................ OK in 0.00 sec
	test case: 'CMAC init #2 AES-192: OK' ........................................................ SKIPPED in 0.00 sec
	test case: 'CMAC init #3 AES-256: OK' ........................................................ SKIPPED in 0.00 sec
	test case: 'CMAC init #4 3DES : OK' .......................................................... SKIPPED in 0.00 sec
	test case: 'CMAC init #5 AES-224: bad key size' .............................................. OK in 0.00 sec
	test case: 'CMAC init #6 AES-0: bad key size' ................................................ OK in 0.00 sec
	test case: 'CMAC init #7 Camellia: wrong cipher' ............................................. SKIPPED in 0.00 sec
	test case: 'CMAC null arguments' ............................................................. SKIPPED in 0.00 sec
	test case: 'CMAC self test' .................................................................. OK in 0.00 sec
mbedgt: all tests finished!
mbedgt: shuffle seed: 0.9263306050
mbedgt: test suite report:
+---------------------+---------------+------------------------------------------------+--------+--------------------+-------------+
| target              | platform_name | test suite                                     | result | elapsed_time (sec) | copy_method |
+---------------------+---------------+------------------------------------------------+--------+--------------------+-------------+
| NRF52840_DK-GCC_ARM | NRF52840_DK   | features-mbedtls-tests-mbedtls-test_suite_cmac | OK     | 27.96              | default     |
+---------------------+---------------+------------------------------------------------+--------+--------------------+-------------+
mbedgt: test suite results: 1 OK
mbedgt: test case report:
+---------------------+---------------+------------------------------------------------+--------------------------------------------------------------+--------+--------+---------+--------------------+
| target              | platform_name | test suite                                     | test case                                                    | passed | failed | result  | elapsed_time (sec) |
+---------------------+---------------+------------------------------------------------+--------------------------------------------------------------+--------+--------+---------+--------------------+
| NRF52840_DK-GCC_ARM | NRF52840_DK   | features-mbedtls-tests-mbedtls-test_suite_cmac | CMAC Multiple Blocks #1 - Multiple 8 byte blocks             | 1      | 0      | OK      | 0.0                |
| NRF52840_DK-GCC_ARM | NRF52840_DK   | features-mbedtls-tests-mbedtls-test_suite_cmac | CMAC Multiple Blocks #2 - Multiple 16 byte blocks            | 1      | 0      | OK      | 0.0                |
| NRF52840_DK-GCC_ARM | NRF52840_DK   | features-mbedtls-tests-mbedtls-test_suite_cmac | CMAC Multiple Blocks #3 - Multiple variable sized blocks     | 1      | 0      | OK      | 0.0                |
| NRF52840_DK-GCC_ARM | NRF52840_DK   | features-mbedtls-tests-mbedtls-test_suite_cmac | CMAC Multiple Blocks #4 - Multiple 8 byte blocks with gaps   | 1      | 0      | OK      | 0.0                |
| NRF52840_DK-GCC_ARM | NRF52840_DK   | features-mbedtls-tests-mbedtls-test_suite_cmac | CMAC Multiple Operations, same key #1 - Empty, empty         | 0      | 0      | SKIPPED | 0.0                |
| NRF52840_DK-GCC_ARM | NRF52840_DK   | features-mbedtls-tests-mbedtls-test_suite_cmac | CMAC Multiple Operations, same key #2 - Empty, 64 byte block | 0      | 0      | SKIPPED | 0.0                |
| NRF52840_DK-GCC_ARM | NRF52840_DK   | features-mbedtls-tests-mbedtls-test_suite_cmac | CMAC Multiple Operations, same key #3 - variable byte blocks | 0      | 0      | SKIPPED | 0.0                |
| NRF52840_DK-GCC_ARM | NRF52840_DK   | features-mbedtls-tests-mbedtls-test_suite_cmac | CMAC Single Blocks #1 - Empty block, no updates              | 1      | 0      | OK      | 0.0                |
| NRF52840_DK-GCC_ARM | NRF52840_DK   | features-mbedtls-tests-mbedtls-test_suite_cmac | CMAC Single Blocks #2 - Single 16 byte block                 | 1      | 0      | OK      | 0.0                |
| NRF52840_DK-GCC_ARM | NRF52840_DK   | features-mbedtls-tests-mbedtls-test_suite_cmac | CMAC Single Blocks #3 - Single 64 byte block                 | 1      | 0      | OK      | 0.0                |
| NRF52840_DK-GCC_ARM | NRF52840_DK   | features-mbedtls-tests-mbedtls-test_suite_cmac | CMAC init #1 AES-128: OK                                     | 1      | 0      | OK      | 0.0                |
| NRF52840_DK-GCC_ARM | NRF52840_DK   | features-mbedtls-tests-mbedtls-test_suite_cmac | CMAC init #2 AES-192: OK                                     | 0      | 0      | SKIPPED | 0.0                |
| NRF52840_DK-GCC_ARM | NRF52840_DK   | features-mbedtls-tests-mbedtls-test_suite_cmac | CMAC init #3 AES-256: OK                                     | 0      | 0      | SKIPPED | 0.0                |
| NRF52840_DK-GCC_ARM | NRF52840_DK   | features-mbedtls-tests-mbedtls-test_suite_cmac | CMAC init #4 3DES : OK                                       | 0      | 0      | SKIPPED | 0.0                |
| NRF52840_DK-GCC_ARM | NRF52840_DK   | features-mbedtls-tests-mbedtls-test_suite_cmac | CMAC init #5 AES-224: bad key size                           | 1      | 0      | OK      | 0.0                |
| NRF52840_DK-GCC_ARM | NRF52840_DK   | features-mbedtls-tests-mbedtls-test_suite_cmac | CMAC init #6 AES-0: bad key size                             | 1      | 0      | OK      | 0.0                |
| NRF52840_DK-GCC_ARM | NRF52840_DK   | features-mbedtls-tests-mbedtls-test_suite_cmac | CMAC init #7 Camellia: wrong cipher                          | 0      | 0      | SKIPPED | 0.0                |
| NRF52840_DK-GCC_ARM | NRF52840_DK   | features-mbedtls-tests-mbedtls-test_suite_cmac | CMAC null arguments                                          | 0      | 0      | SKIPPED | 0.0                |
| NRF52840_DK-GCC_ARM | NRF52840_DK   | features-mbedtls-tests-mbedtls-test_suite_cmac | CMAC self test                                               | 1      | 0      | OK      | 0.0                |
+---------------------+---------------+------------------------------------------------+--------------------------------------------------------------+--------+--------+---------+--------------------+
mbedgt: test case results: 8 SKIPPED / 11 OK
mbedgt: completed in 28.95 sec

Copy link
Contributor

@Patater Patater left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. LGTM

SASI_AES_MODE_CMAC, SASI_AES_PADDING_NONE ) != 0 )
{
return( MBEDTLS_ERR_PLATFORM_HW_ACCEL_FAILED );
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why Astyle does not complain, but this is not really Mbed Coding style.
We should follow more K&R style. See https://os.mbed.com/docs/mbed-os/v5.13/contributing/style.html

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an alternative implementation for code in Mbed TLS, which has its own style rules. Mbed TLS and alternative implementations for it are deliberately excluded from the astyle check in Mbed OS. I don't know if there's a formal rule stated somewhere, but most alternative implementations follow the Mbed TLS style.

@mbed-ci
Copy link

mbed-ci commented Jul 26, 2019

Test run: SUCCESS

Summary: 11 of 11 test jobs passed
Build number : 1
Build artifacts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants