Photo Signature#

ANAFI Ai allows signing photos with the embedded secure element.

The digital signature can prove the authenticity and integrity of any photo taken by the drone; both for the image itself and the associated EXIF and XMP metadata. The metadata includes:

  • the serial number of the drone,

  • the GPS location,

  • the photo capture timestamp,

  • the unique Common Name of the drone certificate.

Once the photo’s digital signature has been verified, one can prove to a client, a court, or any other third party that the picture was taken by a specific drone, at a specific time and location and that this picture has not been modified since.

More security information can be found here: https://developer.parrot.com/docs/airsdk/general/security.html.

Signatures and certificates#

When the digital signature of photos is enabled, photos have an XMP metadata called Xmp.drone-parrot.SecureCn, which value is the common name of the drone certificate.

Each photo that is signed comes with a signature that has the same file name and a signature extension that depends on the photo file format:

Photo file name

Signature file name

0123456_photo.JPG

0123456_photo.SIG

0123456_photo.DNG

0123456_photo.DIG

The signature file contains a digest of the photo, signed using the embedded secure element.

The drone certificate that contains the drone public key is available through the webserver API, with the following URL:

http://<drone_url>/api/v1/secure-element/drone.der

The DER certificate format is a binary file, it can be converted to PEM which is a human-readable format, with OpenSSL:

openssl x509 -inform DER -in drone.der -outform PEM -out drone.pem

This drone certificate is issued by the Parrot Drones Certificate Authority. Any drone certificate authenticity can be verified using this root certificate available here: https://developer.parrot.com/docs/downloads/ca-anafi-ai.pem

Verify a signature#

Signatures can be manually verified using command-line tools such as OpenSSL.

The steps to verify the digital signature of a photo are:

  1. Extract from the photo’s metadata the Common Name of the certificate used to sign the picture,

  2. Verify that the Common Name extracted from the metadata matches the drone certificate serial,

  3. Check that the drone certificate has been issued by the Parrot Drones Certificate Authority,

  4. Check the photo’s digital signature.

First, the Common Name of the drone certificate should be extracted from the Xmp.drone-parrot.SecureCn metadata. This can be done with the exiv2 tool:

With:

  • 0123456_photo.JPG: the photo file

exiv2 -K Xmp.drone-parrot.SecureCn -px 0123456_photo.JPG

The drone certificate serial number can be extracted using OpenSSL:

With:

  • drones.pem: the drone certificate in PEM format

openssl x509 -serial -noout -in drone.pem

After that, the drone certificate must be verified using the Parrot Drones Certificate Authority file:

With:

  • cadrones.pem: the Parrot Drones Certificate Authority file in PEM format

  • drone.pem: the drone certificate in PEM format

openssl verify -CAfile cadrones.pem drone.pem

Now that the drone certificate has been authenticated, the following command can be executed to verify that a photo signature is authentic:

With:

  • drone.pem: the drone certificate in PEM format

  • 0123456_photo.SIG: the photo signature file

  • 0123456_photo.JPG: the photo file

openssl dgst -sha256 \
    -verify <(openssl x509 -in drone.pem -pubkey -noout) \
    -signature 0123456_photo.SIG 0123456_photo.JPG

Felindra#

The felindra Python module provides both a library and an executable to verify the authenticity and integrity of any signed photo taken by an ANAFI Ai. The tool can verify photos remotely on a drone by using the webserver API or offline, by checking a directory and its subdirectories.

The felindra source code is public and available on the Parrot for Developers GitHub: https://github.com/Parrot-Developers/felindra

Download the Python module#

With python3:

python3 -m pip install parrot-felindra

Run the executable#

As a binary:

felindra [-h] [-v] {remote,local}

Or with python3 as a module:

python3 -m felindra [-h] [-v] {remote,local}

Verify signatures on drone#

If no IP address is provided, the script will automatically check common ANAFI Ai IP addresses. Use -S, --skip to skip media that do not have signatures (unsigned photos and videos).

python3 -m felindra remote [ip_addr]

Verify signatures offline#

Verify a single photo

The signature path is optional. If not set, the script will search for <photo_filename_without_ext>.SIG.

python3 -m felindra local \
    <path_to_photo> <path_to_certificate> -s [path_to_signature]

The function returns True if the signature is authentic, otherwise False.

Verify a whole directory including its subdirectories

The script will iterate over all subdirectories of photo_directory, checking for photos.

python3 -m felindra local <photo_directory> <path_to_certificate>

Import the library#

Verify signatures on drone#

from felindra import verify_on_remote

ret = verify_on_remote()

The function returns a Python dictionary that contains all resources and a summary of ok_count, ko_count and unsigned_count.

Here is a sample:

{
    "resources":{
        "1004683_photo.JPG":{
            "signed":true,
            "valid_signature":true
        },
        "1004684_photo.JPG":{
            "signed":false
        },
        "1004685_photo.JPG":{
            "signed":true,
            "valid_signature":false,
            "reason":"invalid signature"
        },
       ...
    },
    "ok_count":19,
    "ko_count":2,
    "unsigned_count":34
}

Verify signatures offline#

Verify a single photo

from felindra import verify_single

ret = verify_single(res_path=..., crt_path=...)

The function returns True if the signature is authentic, otherwise False.

Verify a whole directory and its subdirectories

from felindra import verify_on_disk

ret = verify_on_disk(res_path=..., crt_path=...)

The function returns a Python dictionary that contains all resources and a summary of ok_count, ko_count and unsigned_count.